From 829fe6c6bc33d53fda9c31fd45a37e1df87befff Mon Sep 17 00:00:00 2001
From: mrDarker <mr.darker@163.com>
Date: 星期五, 30 一月 2026 11:16:24 +0800
Subject: [PATCH] Merge branch 'clh' into liuyang

---
 SourceCode/Bond/Servo/CHMPropertyPage.h                |   23 
 SourceCode/Bond/Servo/LoginDlg2.cpp                    |  103 
 SourceCode/Bond/Servo/CSVData.cpp                      |   24 
 SourceCode/Bond/Servo/ProcessJob.cpp                   |    9 
 SourceCode/Bond/Servo/HsmsPassive.cpp                  | 2216 +++++++++
 SourceCode/Bond/Servo/stdafx.h                         |    2 
 SourceCode/Bond/BondEq/AccordionWnd.cpp                |   16 
 SourceCode/Bond/Servo/LoginDlg2.h                      |   30 
 SourceCode/Bond/Servo/CReportEditDlg.h                 |   30 
 SourceCode/Bond/Servo/ProductionStats.h                |   66 
 SourceCode/Bond/Servo/Servo.rc                         |    0 
 SourceCode/Bond/Servo/HmLabel.cpp                      |  177 
 SourceCode/Bond/Servo/ServoDlg.h                       |   33 
 SourceCode/Bond/Servo/HsmsPassive.h                    |  144 
 SourceCode/Bond/Servo/ProcessJob.h                     |    1 
 SourceCode/Bond/Servo/ProductionStats.cpp              |  438 +
 SourceCode/Bond/Servo/CGlass.cpp                       |   36 
 SourceCode/Bond/Servo/CPanelMaster.h                   |    1 
 SourceCode/Bond/Servo/CControlJobManagerDlg.cpp        |   86 
 SourceCode/Bond/Servo/CEventEditDlg.cpp                |   94 
 SourceCode/Bond/Servo/CLoadPort.cpp                    |  108 
 SourceCode/Bond/Servo/ConfigurationProduction.cpp      |   75 
 SourceCode/Bond/x64/Debug/VariableList.txt             |   85 
 SourceCode/Bond/Servo/CPageGlassList.cpp               |  120 
 SourceCode/Bond/Servo/CPageCollectionEvent.h           |    3 
 SourceCode/Bond/Servo/CPageProdOverview.h              |   37 
 SourceCode/Bond/Servo/CPageVarialbles.cpp              |  111 
 SourceCode/Bond/Servo/Servo.vcxproj.filters            |   37 
 SourceCode/Bond/Servo/AccordionWnd.cpp                 |  787 +++
 SourceCode/Bond/x64/Debug/CollectionEventList.txt      |   29 
 SourceCode/Bond/Servo/TopToolbar.cpp                   |   28 
 SourceCode/Bond/Servo/CPageCtrlState.h                 |   41 
 SourceCode/Bond/Servo/CPageReport.cpp                  |  112 
 SourceCode/Bond/Servo/HmLabel.h                        |   33 
 SourceCode/Bond/Servo/HsmsAction.cpp                   |   22 
 SourceCode/Bond/Servo/CPageProdOverview.cpp            |  165 
 SourceCode/Bond/Servo/Servo.vcxproj                    |   35 
 SourceCode/Bond/Servo/resource.h                       |    0 
 SourceCode/Bond/Servo/CHMPropertyDlg.h                 |    3 
 SourceCode/Bond/Servo/CServoUtilsTool.h                |    9 
 SourceCode/Bond/EAPSimulator/CHsmsActive.h             |   48 
 SourceCode/Bond/Servo/CMaster.h                        |   51 
 SourceCode/Bond/Servo/CReportEditDlg.cpp               |   78 
 SourceCode/Bond/BondEq/BondEq.vcxproj                  |    6 
 SourceCode/Bond/Servo/CPageDataVarialbles.cpp          |  220 
 SourceCode/Bond/EAPSimulator/Resource.h                |   23 
 SourceCode/Bond/Servo/CUserManager2Dlg.cpp             |  322 +
 SourceCode/Bond/Servo/ClientListDlg.cpp                |    2 
 SourceCode/Bond/Servo/AlarmManager.h                   |  205 
 SourceCode/Bond/Servo/Model.h                          |   30 
 SourceCode/Bond/Servo/CUserManager2.h                  |   48 
 SourceCode/Bond/Servo/ToolUnits.h                      |    6 
 SourceCode/Bond/Servo/CPageGraph2.cpp                  |   27 
 SourceCode/Bond/Servo/CEventEditDlg.h                  |   37 
 SourceCode/Bond/Servo/CPageCollectionEvent.cpp         |  108 
 SourceCode/Bond/EAPSimulator/EAPSimulator.rc           |    0 
 SourceCode/Bond/Servo/Model.cpp                        |  668 ++
 SourceCode/Bond/Servo/CHMPropertyPage.cpp              |   72 
 SourceCode/Bond/Servo/CUserEdit2Dlg.cpp                |  125 
 SourceCode/Bond/Servo/CPageVarialbles.h                |    5 
 SourceCode/Bond/Servo/CReport.h                        |    3 
 SourceCode/Bond/Servo/AlarmPopupDlg.h                  |   59 
 SourceCode/Bond/Servo/CVariableEditDlg2.cpp            |   89 
 SourceCode/Bond/Servo/CVariable.h                      |    5 
 SourceCode/Bond/Servo/CPageGraph2.h                    |    1 
 SourceCode/Bond/Servo/CPageDataVarialbles.h            |   37 
 SourceCode/Bond/Servo/CReadStep.h                      |    2 
 SourceCode/Bond/Servo/AccordionWnd.h                   |  131 
 SourceCode/Bond/Servo/CCollectionEvent.cpp             |    4 
 SourceCode/Bond/Servo/CUserXLogDlg.cpp                 |  178 
 SourceCode/Bond/x64/Debug/AlarmList.txt                |    1 
 SourceCode/Bond/Servo/Configuration.h                  |    7 
 SourceCode/Bond/EAPSimulator/CHsmsActive.cpp           |  196 
 SourceCode/Bond/Servo/EqsGraphWnd.cpp                  |  315 
 SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.cpp       |  349 +
 SourceCode/Bond/Servo/ToolUnits.cpp                    |   65 
 SourceCode/Bond/USERXLibrary/UserXAPI.h                |  136 
 SourceCode/Bond/Servo/CBonder.h                        |    2 
 SourceCode/Bond/Servo/CReport.cpp                      |    6 
 SourceCode/Bond/x64/Debug/test.ini                     |   10 
 SourceCode/Bond/Servo/CUserManager2.cpp                |  250 +
 SourceCode/Bond/Servo/CPanelProduction.h               |   64 
 SourceCode/Bond/x64/Debug/DataVariableList.txt         |   98 
 SourceCode/Bond/Servo/CPageReport.h                    |    3 
 SourceCode/Bond/Servo/CMyStatusbar.cpp                 |   35 
 SourceCode/Bond/Servo/CEquipment.h                     |   60 
 SourceCode/Bond/Servo/CDataVariable.h                  |   13 
 SourceCode/Bond/Servo/CParam.cpp                       |    4 
 SourceCode/Bond/Servo/AlarmManager.cpp                 |  420 +
 SourceCode/Bond/Servo/CMyStatusbar.h                   |    3 
 .gitignore                                             |    2 
 SourceCode/Bond/Servo/CUserManager2Dlg.h               |   45 
 SourceCode/Bond/Servo/Servo.vcxproj.user               |    2 
 SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.h         |   14 
 SourceCode/Bond/Servo/Servo.cpp                        |  140 
 SourceCode/Bond/Servo/CPanelMaster.cpp                 |   18 
 SourceCode/Bond/Servo/CPageGlassList.h                 |    3 
 SourceCode/Bond/Servo/CHMPropertyDlg.cpp               |  104 
 SourceCode/Bond/Servo/CEquipment.cpp                   |  184 
 SourceCode/Bond/Servo/CMaster.cpp                      | 1055 +++-
 SourceCode/Bond/Servo/AlarmPopupDlg.cpp                |  321 +
 SourceCode/Bond/Servo/CParam.h                         |    4 
 SourceCode/Bond/x64/Debug/ReportList.txt               |   54 
 SourceCode/Bond/Servo/CReadStep.cpp                    |    1 
 SourceCode/Bond/Servo/EqsGraphWnd.h                    |   36 
 SourceCode/Bond/Servo/CUserXLogDlg.h                   |   42 
 SourceCode/Bond/Servo/CLoadPort.h                      |   10 
 Document/EventSummary_v2.1.xlsx                        |    0 
 SourceCode/Bond/Servo/CPanelProduction.cpp             |  236 +
 SourceCode/Bond/Servo/ServoDlg.cpp                     |  457 +
 SourceCode/Bond/Servo/CPageGraph1.cpp                  |   60 
 SourceCode/Bond/Servo/PageRecipe.cpp                   |   37 
 SourceCode/Bond/Servo/CCollectionEvent.h               |    6 
 SourceCode/Bond/Servo/CPageCtrlState.cpp               |  157 
 SourceCode/Bond/Servo/PortConfigurationDlg.cpp         |   62 
 Document/VariableList.txt                              |   14 
 SourceCode/Bond/EAPSimulator/CPJsDlg.cpp               |    2 
 SourceCode/Bond/BLControlsSDK/include/BLLabel.h        |    2 
 SourceCode/Bond/Servo/CServoUtilsTool.cpp              |   69 
 SourceCode/Bond/Servo/CVariableEditDlg2.h              |   34 
 SourceCode/Bond/x64/Release/AlarmList.txt              |    1 
 Document/Panel Bonder八零联合 SecsTest CheckList_v3.0.xlsx |    0 
 SourceCode/Bond/Servo/CBonder.cpp                      |  236 
 SourceCode/Bond/Servo/CUserEdit2Dlg.h                  |   23 
 SourceCode/Bond/Servo/Common.h                         |   31 
 125 files changed, 11,790 insertions(+), 1,668 deletions(-)

diff --git a/.gitignore b/.gitignore
index e97c776..7e74b67 100644
--- a/.gitignore
+++ b/.gitignore
@@ -63,3 +63,5 @@
 SourceCode/Bond/x64/Debug/HsmsPassive.cache
 SourceCode/Bond/x64/Debug/MasterState.dat
 SourceCode/Bond/x64/Debug/Recipe/EQ10_Unit0.recipelist
+SourceCode/Bond/UserX/
+Document/~$Panel Bonder鍏浂鑱斿悎 SecsTest CheckList_v3.0.xlsx
diff --git a/Document/EventSummary_v2.1.xlsx b/Document/EventSummary_v2.1.xlsx
new file mode 100644
index 0000000..9948a9e
--- /dev/null
+++ b/Document/EventSummary_v2.1.xlsx
Binary files differ
diff --git "a/Document/Panel Bonder\345\205\253\351\233\266\350\201\224\345\220\210 SecsTest CheckList_v3.0.xlsx" "b/Document/Panel Bonder\345\205\253\351\233\266\350\201\224\345\220\210 SecsTest CheckList_v3.0.xlsx"
index b8f9219..d3d8537 100644
--- "a/Document/Panel Bonder\345\205\253\351\233\266\350\201\224\345\220\210 SecsTest CheckList_v3.0.xlsx"
+++ "b/Document/Panel Bonder\345\205\253\351\233\266\350\201\224\345\220\210 SecsTest CheckList_v3.0.xlsx"
Binary files differ
diff --git a/Document/VariableList.txt b/Document/VariableList.txt
index 1c92863..9db9582 100644
--- a/Document/VariableList.txt
+++ b/Document/VariableList.txt
@@ -1,6 +1,16 @@
 SVID,SV Name,SV Format,SV Remark
-100,PortTransferState,U1,"0=OutOfService\r\n1=TransferBlocked\r\n2=ReadyToLoad\r\n3=ReadyToUnload\r\n4=InService\r\n5=TransferReady"
-300,AccessMode,U1,"1=Manual\r\n2=Auto"
+10000,CarrierID_P1,A50,Carrier ID for Port 1
+10001,CarrierID_P2,A50,Carrier ID for Port 2
+10002,CarrierID_P3,A50,Carrier ID for Port 3
+10003,CarrierID_P4,A50,Carrier ID for Port 4
+100,PortTransferState_P1,U1,1=LoadReady;2=Loaded;3=InUse;4=UnloadReady;5=Empty;6=Blocked
+101,PortTransferState_P2,U1,1=LoadReady;2=Loaded;3=InUse;4=UnloadReady;5=Empty;6=Blocked
+102,PortTransferState_P3,U1,1=LoadReady;2=Loaded;3=InUse;4=UnloadReady;5=Empty;6=Blocked
+103,PortTransferState_P4,U1,1=LoadReady;2=Loaded;3=InUse;4=UnloadReady;5=Empty;6=Blocked
+300,AccessMode_P1,U1,0=OutOfService;1=TransferBlocked;2=ReadyToLoad;3=ReadyToUnload;4=InService;5=TransferReady
+301,AccessMode_P2,U1,0=OutOfService;1=TransferBlocked;2=ReadyToLoad;3=ReadyToUnload;4=InService;5=TransferReady
+302,AccessMode_P3,U1,0=OutOfService;1=TransferBlocked;2=ReadyToLoad;3=ReadyToUnload;4=InService;5=TransferReady
+303,AccessMode_P4,U1,0=OutOfService;1=TransferBlocked;2=ReadyToLoad;3=ReadyToUnload;4=InService;5=TransferReady
 500,Clock,A50,
 600,CurrentControlState,U1,"0:Offline:equipment\r\n1:Offline-Attempt\r\n2:Online\r\n3:Offline:host\r\n4:Online:Local\r\n5:Online:Remote"
 601,PreviousControlState,U1,
diff --git a/SourceCode/Bond/BLControlsSDK/include/BLLabel.h b/SourceCode/Bond/BLControlsSDK/include/BLLabel.h
index 88ff5d0..5089fa8 100644
--- a/SourceCode/Bond/BLControlsSDK/include/BLLabel.h
+++ b/SourceCode/Bond/BLControlsSDK/include/BLLabel.h
@@ -1,4 +1,4 @@
-#if !defined(AFX_BLLABEL_H__A4EABEC5_2E8C_11D1_B79F_00805F9ECE10__INCLUDED_)
+锘�#if !defined(AFX_BLLABEL_H__A4EABEC5_2E8C_11D1_B79F_00805F9ECE10__INCLUDED_)
 #define AFX_BLLABEL_H__A4EABEC5_2E8C_11D1_B79F_00805F9ECE10__INCLUDED_
 
 #if _MSC_VER >= 1000
diff --git a/SourceCode/Bond/BondEq/AccordionWnd.cpp b/SourceCode/Bond/BondEq/AccordionWnd.cpp
index 5da6615..026628e 100644
--- a/SourceCode/Bond/BondEq/AccordionWnd.cpp
+++ b/SourceCode/Bond/BondEq/AccordionWnd.cpp
@@ -47,9 +47,16 @@
 
 BOOL CAccordionWnd::RegisterWndClass()
 {
-	WNDCLASS wc;
+	WNDCLASS wcExisting = {};
+	HINSTANCE hInstance = AfxGetInstanceHandle();
+	if (::GetClassInfo(hInstance, ACCORDIONWND_CLASS, &wcExisting) ||
+		::GetClassInfo(NULL, ACCORDIONWND_CLASS, &wcExisting)) {
+		return TRUE;
+	}
+
+	WNDCLASS wc = {};
 	wc.lpszClassName = ACCORDIONWND_CLASS;
-	wc.hInstance = AfxGetInstanceHandle();
+	wc.hInstance = hInstance;
 	wc.lpfnWndProc = WindowProc;
 	wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
 	wc.hIcon = 0;
@@ -60,7 +67,10 @@
 	wc.cbWndExtra = 0;
 
 	// 注册自定义类
-	return (::RegisterClass(&wc) != 0);
+	if (::RegisterClass(&wc) != 0) {
+		return TRUE;
+	}
+	return (::GetLastError() == ERROR_CLASS_ALREADY_EXISTS);
 }
 
 CAccordionWnd * CAccordionWnd::FromHandle(HWND hWnd)
diff --git a/SourceCode/Bond/BondEq/BondEq.vcxproj b/SourceCode/Bond/BondEq/BondEq.vcxproj
index 06553f1..432fb35 100644
--- a/SourceCode/Bond/BondEq/BondEq.vcxproj
+++ b/SourceCode/Bond/BondEq/BondEq.vcxproj
@@ -21,7 +21,7 @@
   <PropertyGroup Label="Globals">
     <ProjectGuid>{7864134E-C538-4C0F-AF24-215FFCCBBAB4}</ProjectGuid>
     <RootNamespace>BondServo</RootNamespace>
-    <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
+    <WindowsTargetPlatformVersion>10.0.22000.0</WindowsTargetPlatformVersion>
     <Keyword>MFCProj</Keyword>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
@@ -43,14 +43,14 @@
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
     <ConfigurationType>Application</ConfigurationType>
     <UseDebugLibraries>true</UseDebugLibraries>
-    <PlatformToolset>v140</PlatformToolset>
+    <PlatformToolset>v142</PlatformToolset>
     <CharacterSet>MultiByte</CharacterSet>
     <UseOfMfc>Dynamic</UseOfMfc>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
     <ConfigurationType>Application</ConfigurationType>
     <UseDebugLibraries>false</UseDebugLibraries>
-    <PlatformToolset>v140</PlatformToolset>
+    <PlatformToolset>v142</PlatformToolset>
     <WholeProgramOptimization>true</WholeProgramOptimization>
     <CharacterSet>MultiByte</CharacterSet>
     <UseOfMfc>Dynamic</UseOfMfc>
diff --git a/SourceCode/Bond/EAPSimulator/CHsmsActive.cpp b/SourceCode/Bond/EAPSimulator/CHsmsActive.cpp
index b334708..a2539da 100644
--- a/SourceCode/Bond/EAPSimulator/CHsmsActive.cpp
+++ b/SourceCode/Bond/EAPSimulator/CHsmsActive.cpp
@@ -1,9 +1,22 @@
-#include "pch.h"
+锘�#include "pch.h"
 #include "CHsmsActive.h"
 #include "Log.h"
 
 
-static unsigned int DATAID = 1;
+static unsigned short DATAID = 1;
+
+// Truncated SECS message logging to avoid overly long strings crashing UI/log
+static void LogSecsMessageBrief(const char* tag, IMessage* pMessage, size_t maxLen = 1024)
+{
+	if (pMessage == nullptr) return;
+	const char* msgStr = pMessage->toString();
+	if (msgStr == nullptr) return;
+	std::string buf(msgStr);
+	if (buf.size() > maxLen) {
+		buf = buf.substr(0, maxLen) + "...<truncated>";
+	}
+	LOGI("%s%s", tag, buf.c_str());
+}
 
 CHsmsActive::CHsmsActive()
 {
@@ -64,9 +77,9 @@
 		HEADER* pHeader = pMessage->getHeader();
 		int nStream = (pHeader->stream & 0x7F);
 
-		TRACE("收到消息 S%dF%d================\n", pHeader->stream & 0x7F, pHeader->function);
-		TRACE("Body:%s\n", pMessage->toString());
-		LOGI("onRecvDataMessage(%s).", pMessage->toString());
+		TRACE("鏀跺埌娑堟伅 S%dF%d================\n", pHeader->stream & 0x7F, pHeader->function);
+		LogSecsMessageBrief("Body:", pMessage);
+		LogSecsMessageBrief("onRecvDataMessage:", pMessage);
 
 		if (nStream == 5 && pHeader->function == 1) {
 			// S5F1
@@ -140,6 +153,48 @@
 	m_pActive->sendMessage(pMessage);
 	HSMS_Destroy1Message(pMessage);
 
+	return 0;
+}
+
+int CHsmsActive::hsmsRequestOnline()
+{
+	IMessage* pMessage = nullptr;
+	int nRet = HSMS_Create1Message(pMessage, m_nSessionId, 1 | REPLY, 17, ++m_nSystemByte);
+	m_pActive->sendMessage(pMessage);
+	HSMS_Destroy1Message(pMessage);
+	return 0;
+}
+
+int CHsmsActive::hsmsRequestOffline()
+{
+	IMessage* pMessage = nullptr;
+	int nRet = HSMS_Create1Message(pMessage, m_nSessionId, 1 | REPLY, 15, ++m_nSystemByte);
+	m_pActive->sendMessage(pMessage);
+	HSMS_Destroy1Message(pMessage);
+	return 0;
+}
+
+int CHsmsActive::hsmsGoLocal()
+{
+	IMessage* pMessage = nullptr;
+	int nRet = HSMS_Create1Message(pMessage, m_nSessionId, 2 | REPLY, 41, ++m_nSystemByte);
+	ISECS2Item* pBody = pMessage->getBody();
+	pBody->addItem("GoLocal", "RCMD");
+	pBody->addItem(); // L: empty params
+	m_pActive->sendMessage(pMessage);
+	HSMS_Destroy1Message(pMessage);
+	return 0;
+}
+
+int CHsmsActive::hsmsGoRemote()
+{
+	IMessage* pMessage = nullptr;
+	int nRet = HSMS_Create1Message(pMessage, m_nSessionId, 2 | REPLY, 41, ++m_nSystemByte);
+	ISECS2Item* pBody = pMessage->getBody();
+	pBody->addItem("GoRemote", "RCMD");
+	pBody->addItem(); // L: empty params
+	m_pActive->sendMessage(pMessage);
+	HSMS_Destroy1Message(pMessage);
 	return 0;
 }
 
@@ -300,10 +355,70 @@
 	IMessage* pMessage = nullptr;
 	int nRet = HSMS_Create1Message(pMessage, m_nSessionId, 1 | REPLY, 3, ++m_nSystemByte);
 
-	pMessage->getBody()->addU4Item(SVID, "SVID");
+	pMessage->getBody()->addU2Item(static_cast<unsigned short>(SVID), "SVID");
 	m_pActive->sendMessage(pMessage);
 	HSMS_Destroy1Message(pMessage);
 
+	return 0;
+}
+
+int CHsmsActive::hsmsQueryAllStatusVariables()
+{
+	IMessage* pMessage = nullptr;
+	int nRet = HSMS_Create1Message(pMessage, m_nSessionId, 1 | REPLY, 11, ++m_nSystemByte);
+	// Host sends L:0 (empty list) to request all SVIDs.
+	pMessage->getBody()->addItem(); // empty list
+	m_pActive->sendMessage(pMessage);
+	HSMS_Destroy1Message(pMessage);
+	return 0;
+}
+
+int CHsmsActive::hsmsQueryAllDataVariables()
+{
+	IMessage* pMessage = nullptr;
+	int nRet = HSMS_Create1Message(pMessage, m_nSessionId, 1 | REPLY, 21, ++m_nSystemByte);
+	// Host sends L:0 (empty list) to request all DVIDs.
+	pMessage->getBody()->addItem(); // empty list
+	m_pActive->sendMessage(pMessage);
+	HSMS_Destroy1Message(pMessage);
+	return 0;
+}
+
+int CHsmsActive::hsmsQueryAllCollectionEvents()
+{
+	IMessage* pMessage = nullptr;
+	int nRet = HSMS_Create1Message(pMessage, m_nSessionId, 1 | REPLY, 23, ++m_nSystemByte);
+	// Host sends L:0 (empty list) to request all CEIDs.
+	pMessage->getBody()->addItem(); // empty list
+	m_pActive->sendMessage(pMessage);
+	HSMS_Destroy1Message(pMessage);
+	return 0;
+}
+
+int CHsmsActive::hsmsEquipmentConstantRequest(const std::vector<unsigned short>& ecids)
+{
+	IMessage* pMessage = nullptr;
+	int nRet = HSMS_Create1Message(pMessage, m_nSessionId, 2 | REPLY, 13, ++m_nSystemByte);
+	for (auto id : ecids) {
+		pMessage->getBody()->addU2Item(id, "ECID");
+	}
+	m_pActive->sendMessage(pMessage);
+	HSMS_Destroy1Message(pMessage);
+	return 0;
+}
+
+int CHsmsActive::hsmsEquipmentConstantSend(const std::vector<std::pair<unsigned short, std::string>>& ecidValues)
+{
+	IMessage* pMessage = nullptr;
+	int nRet = HSMS_Create1Message(pMessage, m_nSessionId, 2 | REPLY, 15, ++m_nSystemByte);
+	ISECS2Item* pBody = pMessage->getBody();
+	for (const auto& kv : ecidValues) {
+		ISECS2Item* pEntry = pBody->addItem();
+		pEntry->addU2Item(kv.first, "ECID");
+		pEntry->addItem(kv.second.c_str(), "ECV");
+	}
+	m_pActive->sendMessage(pMessage);
+	HSMS_Destroy1Message(pMessage);
 	return 0;
 }
 
@@ -314,6 +429,33 @@
 	m_pActive->sendMessage(pMessage);
 	HSMS_Destroy1Message(pMessage);
 
+	return 0;
+}
+
+int CHsmsActive::hsmsDeletePPID(const std::vector<std::string>& ppids)
+{
+	IMessage* pMessage = nullptr;
+	int nRet = HSMS_Create1Message(pMessage, m_nSessionId, 7 | REPLY, 17, ++m_nSystemByte);
+	if (nRet != 0 || pMessage == nullptr) return -1;
+	ISECS2Item* pBody = pMessage->getBody();
+	for (const auto& ppid : ppids) {
+		pBody->addItem(ppid.c_str(), "PPID");
+	}
+	m_pActive->sendMessage(pMessage);
+	HSMS_Destroy1Message(pMessage);
+	return 0;
+}
+
+int CHsmsActive::hsmsProcessProgramRequest(const char* pszPPID)
+{
+	if (pszPPID == nullptr || strlen(pszPPID) == 0) return -1;
+	IMessage* pMessage = nullptr;
+	if (HSMS_Create1Message(pMessage, m_nSessionId, 7 | REPLY, 5, ++m_nSystemByte) != 0 || pMessage == nullptr) {
+		return -1;
+	}
+	pMessage->getBody()->setString(pszPPID, "PPID");
+	m_pActive->sendMessage(pMessage);
+	HSMS_Destroy1Message(pMessage);
 	return 0;
 }
 
@@ -349,12 +491,50 @@
 	return hsmsCarrierActionRequest(DATAID, "CarrierRelease", pszCarrierId, PTN);
 }
 
+int CHsmsActive::hsmsProceedWithSlotMap(unsigned int DATAID,
+	const char* pszCarrierId,
+	unsigned char PTN,
+	const char* pszLotId,
+	const std::vector<std::string>& panelIds,
+	const std::vector<unsigned char>& slotMap)
+{
+	IMessage* pMessage = nullptr;
+	int nRet = HSMS_Create1Message(pMessage, m_nSessionId, 3 | REPLY, 17, ++m_nSystemByte);
+	if (nRet != 0 || pMessage == nullptr) {
+		return nRet;
+	}
+
+	pMessage->getBody()->addU4Item(DATAID, "DATAID");
+	pMessage->getBody()->addItem("ProceedWithSlotMap", "CARRIERACTION");
+	pMessage->getBody()->addItem(pszCarrierId, "CARRIERID");
+	pMessage->getBody()->addU1Item(PTN, "PTN");
+
+	// Extended params (currently not parsed by Servo side): { LOTID, PANELID_LIST, SLOTMAP_LIST }
+	ISECS2Item* pParams = pMessage->getBody()->addItem(); // L
+	pParams->addItem(pszLotId != nullptr ? pszLotId : "", "LOTID");
+
+	ISECS2Item* pPanelList = pParams->addItem(); // L
+	for (const auto& id : panelIds) {
+		pPanelList->addItem(id.c_str(), "PANELID");
+	}
+
+	ISECS2Item* pSlotMapList = pParams->addItem(); // L
+	for (auto v : slotMap) {
+		pSlotMapList->addU1Item(v, "SLOTSTATE");
+	}
+
+	m_pActive->sendMessage(pMessage);
+	HSMS_Destroy1Message(pMessage);
+
+	return 0;
+}
+
 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");
+	pMessage->getBody()->addU2Item(++DATAID, "DATAID");
 	auto itemPjs = pMessage->getBody()->addItem();
 	for (auto pj : pjs) {
 		auto itemPj = itemPjs->addItem();
@@ -434,7 +614,7 @@
 	return 0;
 }
 
-// 通用的reply ack函数
+// 閫氱敤鐨剅eply ack鍑芥暟
 void CHsmsActive::replyAck(int s, int f, unsigned int systemBytes, BYTE ack, const char* pszAckName)
 {
 	IMessage* pMessage = NULL;
diff --git a/SourceCode/Bond/EAPSimulator/CHsmsActive.h b/SourceCode/Bond/EAPSimulator/CHsmsActive.h
index 29abeb5..9e8eb07 100644
--- a/SourceCode/Bond/EAPSimulator/CHsmsActive.h
+++ b/SourceCode/Bond/EAPSimulator/CHsmsActive.h
@@ -1,4 +1,4 @@
-#pragma once
+锘�#pragma once
 #include <string>
 #include <vector>
 #include <map>
@@ -7,9 +7,18 @@
 #include "ProcessJob.h"
 
 
-#define SVID_CJobSpace				5001
+#define SVID_ControlState           600
+#define SVID_CurrentProcessState    700
+#define SVID_CJobSpace            5001
 #define SVID_PJobSpace				5002
 #define SVID_PJobQueued				5003
+#define SVID_EQPPExecName           801
+#define SVID_Bonder1CurrentRecipe   8100
+#define SVID_Bonder2CurrentRecipe   8101
+#define SVID_VacuumBakeCurrentRecipe 8102
+#define SVID_BakeCoolingCurrentRecipe 8103
+#define SVID_MeasurementCurrentRecipe 8104
+#define SVID_EFEMCurrentRecipe      8105
 
 
 typedef std::function<void(void* pFrom, ACTIVESTATE state)> STATECHANGED;
@@ -36,11 +45,19 @@
 	// Deselect Request
 	int hsmsDeselectRequest();
 
-	// 建立通讯(S1F13)
+	// 寤虹珛閫氳(S1F13)
 	int hsmsEstablishCommunications();
 
 	// Are You There
 	int hsmsAreYouThere();
+
+	// ControlState: Request Online/Offline (S1F17 / S1F15)
+	int hsmsRequestOnline();
+	int hsmsRequestOffline();
+
+	// ControlState: GoLocal/GoRemote (S2F41)
+	int hsmsGoLocal();
+	int hsmsGoRemote();
 
 	// Date time sync
 	int hsmsDatetimeSync();
@@ -63,18 +80,25 @@
 	// Configure Spooling
 	int hsmsConfigureSpooling(std::map<unsigned int, std::set<unsigned int>>& spoolingConfig);
 
-	// 发送或清空缓存的消息
+	// 鍙戦�佹垨娓呯┖缂撳瓨鐨勬秷鎭�
 	int hsmsTransmitSpooledData();
 	int hsmsPurgeSpooledData();
 
-	// 查询变量
+	// 鏌ヨ鍙橀噺
 	int hsmsSelectedEquipmentStatusRequest(unsigned int SVID);
+	int hsmsQueryAllStatusVariables();      // S1F11
+	int hsmsQueryAllDataVariables();        // S1F21
+	int hsmsQueryAllCollectionEvents();     // S1F23
+	int hsmsEquipmentConstantRequest(const std::vector<unsigned short>& ecids); // S2F13
+	int hsmsEquipmentConstantSend(const std::vector<std::pair<unsigned short, std::string>>& ecidValues); // S2F15
 
-	// 查询PPID List
+	// 鏌ヨPPID List
 	int hsmsQueryPPIDList();
+	int hsmsDeletePPID(const std::vector<std::string>& ppids); // S7F17
+	int hsmsProcessProgramRequest(const char* pszPPID); // S7F5
 
 	// S3F17
-	// 卡匣动作请求
+	// 鍗″專鍔ㄤ綔璇锋眰
 	int hsmsCarrierActionRequest(unsigned int DATAID, 
 		const char* pszCarrierAction,
 		const char* pszCarrierId,
@@ -82,7 +106,13 @@
 	int hsmsProceedWithCarrier(unsigned int DATAID,
 		const char* pszCarrierId,
 		unsigned char PTN);
-	int CHsmsActive::hsmsCarrierRelease(unsigned int DATAID,
+	int hsmsProceedWithSlotMap(unsigned int DATAID,
+		const char* pszCarrierId,
+		unsigned char PTN,
+		const char* pszLotId,
+		const std::vector<std::string>& panelIds,
+		const std::vector<unsigned char>& slotMap);
+	int hsmsCarrierRelease(unsigned int DATAID,
 		const char* pszCarrierId,
 		unsigned char PTN);
 
@@ -92,7 +122,7 @@
 	// S14F9
 	int hsmsCreateControlJob(const char* pszControlJobId, std::vector<std::string>& processJobIds);
 
-	// 通过的reply函数
+	// 閫氳繃鐨剅eply鍑芥暟
 	void replyAck(int s, int f, unsigned int systemBytes, BYTE ack, const char* pszAckName);
 
 	// reply ack0
diff --git a/SourceCode/Bond/EAPSimulator/CPJsDlg.cpp b/SourceCode/Bond/EAPSimulator/CPJsDlg.cpp
index 212291f..9f1c61b 100644
--- a/SourceCode/Bond/EAPSimulator/CPJsDlg.cpp
+++ b/SourceCode/Bond/EAPSimulator/CPJsDlg.cpp
@@ -98,7 +98,7 @@
 	{
 		SERVO::CProcessJob* pj = new SERVO::CProcessJob("PJ0001");
 		std::vector<uint8_t> slots1{ 1, 2, 3 };
-		pj->addCarrier("CID1001", slots1);
+		pj->addCarrier("Test-Cassette-001", slots1);
 		pj->setRecipe(SERVO::RecipeMethod::NoTuning, "P1001");
 		m_pjs.push_back(pj);
 	}
diff --git a/SourceCode/Bond/EAPSimulator/EAPSimulator.rc b/SourceCode/Bond/EAPSimulator/EAPSimulator.rc
index 53eb764..92e768e 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 e15ff68..a52341f 100644
--- a/SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.cpp
+++ b/SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.cpp
@@ -9,6 +9,8 @@
 #include "afxdialogex.h"
 #include "Common.h"
 #include <regex>
+#include <string>
+#include <vector>
 #include "CTerminalDisplayDlg.h"
 #include "CEDEventReportDlg.h"
 #include "CDefineReportsDlg.h"
@@ -89,12 +91,26 @@
 	ON_BN_CLICKED(IDC_BUTTON_TRANSMIT_SPOOLED_DATA, &CEAPSimulatorDlg::OnBnClickedButtonTransmitSpooledData)
 	ON_BN_CLICKED(IDC_BUTTON_PURGE_SPOOLED_DATA, &CEAPSimulatorDlg::OnBnClickedButtonPurgeSpooledData)
 	ON_BN_CLICKED(IDC_BUTTON_QUERY_PPID_LIST, &CEAPSimulatorDlg::OnBnClickedButtonQueryPpidList)
+	ON_BN_CLICKED(IDC_BUTTON_DELETE_PPID, &CEAPSimulatorDlg::OnBnClickedButtonDeletePpid)
 	ON_BN_CLICKED(IDC_BUTTON_PROCEED_WITH_CARRIER, &CEAPSimulatorDlg::OnBnClickedButtonProceedWithCarrier)
+	ON_BN_CLICKED(IDC_BUTTON_PROCEED_WITH_SLOTMAP, &CEAPSimulatorDlg::OnBnClickedButtonProceedWithSlotMap)
 	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)
+	ON_BN_CLICKED(IDC_BUTTON_CTRL_OFFLINE, &CEAPSimulatorDlg::OnBnClickedButtonCtrlOffline)
+	ON_BN_CLICKED(IDC_BUTTON_CTRL_ONLINE_LOCAL, &CEAPSimulatorDlg::OnBnClickedButtonCtrlOnlineLocal)
+	ON_BN_CLICKED(IDC_BUTTON_CTRL_ONLINE_REMOTE, &CEAPSimulatorDlg::OnBnClickedButtonCtrlOnlineRemote)
+	ON_BN_CLICKED(IDC_BUTTON_QUERY_CONTROL_STATE, &CEAPSimulatorDlg::OnBnClickedButtonQueryControlState)
+	ON_BN_CLICKED(IDC_BUTTON_QUERY_PROCESS_STATE, &CEAPSimulatorDlg::OnBnClickedButtonQueryProcessState)
+	ON_BN_CLICKED(IDC_BUTTON_QUERY_ALL_SVID, &CEAPSimulatorDlg::OnBnClickedButtonQueryAllSvid)
+	ON_BN_CLICKED(IDC_BUTTON_QUERY_ALL_DVID, &CEAPSimulatorDlg::OnBnClickedButtonQueryAllDvid)
+	ON_BN_CLICKED(IDC_BUTTON_QUERY_ALL_CEID, &CEAPSimulatorDlg::OnBnClickedButtonQueryAllCeid)
+	ON_BN_CLICKED(IDC_BUTTON_QUERY_ALL_ECID, &CEAPSimulatorDlg::OnBnClickedButtonQueryAllEcid)
+	ON_BN_CLICKED(IDC_BUTTON_SET_ECID, &CEAPSimulatorDlg::OnBnClickedButtonSetEcid)
+	ON_BN_CLICKED(IDC_BUTTON_QUERY_CURRENT_RECIPE, &CEAPSimulatorDlg::OnBnClickedButtonQueryCurrentRecipe)
+	ON_BN_CLICKED(IDC_BUTTON_PP_REQUEST, &CEAPSimulatorDlg::OnBnClickedButtonPpRequest)
 END_MESSAGE_MAP()
 
 
@@ -177,6 +193,169 @@
 	//  鎵ц姝ゆ搷浣�
 	SetIcon(m_hIcon, TRUE);			// 璁剧疆澶у浘鏍�
 	SetIcon(m_hIcon, FALSE);		// 璁剧疆灏忓浘鏍�
+
+	// Add missing test button at runtime (resource file is UTF-16, keep it unchanged)
+	{
+		CRect rc(126, 120, 126 + 108, 120 + 14); // dialog units
+		MapDialogRect(&rc);
+		HWND hBtn = ::CreateWindow(_T("BUTTON"), _T("S3F17_ProceedWithSlotMap"),
+			WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
+			rc.left, rc.top, rc.Width(), rc.Height(),
+			m_hWnd, (HMENU)IDC_BUTTON_PROCEED_WITH_SLOTMAP, AfxGetInstanceHandle(), nullptr);
+		if (hBtn != nullptr) {
+			::SendMessage(hBtn, WM_SETFONT, (WPARAM)GetFont()->GetSafeHandle(), TRUE);
+		}
+	}
+	// ControlState test buttons at runtime (resource file is UTF-16, keep it unchanged)
+	{
+		const int y = 120;
+		const int w = 70;
+		const int h = 14;
+		const int gap = 3;
+
+		struct BtnDef { int x; int id; const TCHAR* text; };
+		BtnDef defs[] = {
+			{ 238, IDC_BUTTON_CTRL_OFFLINE, _T("Ctrl Offline") },
+			{ 238 + (w + gap), IDC_BUTTON_CTRL_ONLINE_LOCAL, _T("Ctrl Local") },
+			{ 238 + 2 * (w + gap), IDC_BUTTON_CTRL_ONLINE_REMOTE, _T("Ctrl Remote") },
+		};
+
+		for (const auto& d : defs) {
+			CRect rc(d.x, y, d.x + w, y + h); // dialog units
+			MapDialogRect(&rc);
+			HWND hBtn = ::CreateWindow(_T("BUTTON"), d.text,
+				WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
+				rc.left, rc.top, rc.Width(), rc.Height(),
+				m_hWnd, (HMENU)d.id, AfxGetInstanceHandle(), nullptr);
+			if (hBtn != nullptr) {
+				::SendMessage(hBtn, WM_SETFONT, (WPARAM)GetFont()->GetSafeHandle(), TRUE);
+			}
+		}
+	}
+	// S1F3 Query ControlState (SVID=600) button (runtime add to avoid touching UTF-16 RC)
+	{
+		// Place on its own row to avoid overlapping with control-mode buttons.
+		CRect rc(14, 136, 14 + 140, 136 + 14); // dialog units
+		MapDialogRect(&rc);
+		HWND hBtn = ::CreateWindow(_T("BUTTON"), _T("S1F3_QueryControlState"),
+			WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
+			rc.left, rc.top, rc.Width(), rc.Height(),
+			m_hWnd, (HMENU)IDC_BUTTON_QUERY_CONTROL_STATE, AfxGetInstanceHandle(), nullptr);
+		if (hBtn != nullptr) {
+			::SendMessage(hBtn, WM_SETFONT, (WPARAM)GetFont()->GetSafeHandle(), TRUE);
+		}
+	}
+	// S1F3 Query ProcessState (SVID=700) button
+	{
+		CRect rc(14 + 140 + 5, 136, 14 + 140 + 5 + 140, 136 + 14); // dialog units, same row offset
+		MapDialogRect(&rc);
+		HWND hBtn = ::CreateWindow(_T("BUTTON"), _T("S1F3_QueryProcessState"),
+			WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
+			rc.left, rc.top, rc.Width(), rc.Height(),
+			m_hWnd, (HMENU)IDC_BUTTON_QUERY_PROCESS_STATE, AfxGetInstanceHandle(), nullptr);
+		if (hBtn != nullptr) {
+			::SendMessage(hBtn, WM_SETFONT, (WPARAM)GetFont()->GetSafeHandle(), TRUE);
+		}
+	}
+	// S1F11 QueryAllSVID
+	{
+		CRect rc(14 + 140 + 5 + 140 + 5, 136, 14 + 140 + 5 + 140 + 5 + 140, 136 + 14); // dialog units, next row
+		MapDialogRect(&rc);
+		HWND hBtn = ::CreateWindow(_T("BUTTON"), _T("S1F11_QueryAllSVID"),
+			WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
+			rc.left, rc.top, rc.Width(), rc.Height(),
+			m_hWnd, (HMENU)IDC_BUTTON_QUERY_ALL_SVID, AfxGetInstanceHandle(), nullptr);
+		if (hBtn != nullptr) {
+			::SendMessage(hBtn, WM_SETFONT, (WPARAM)GetFont()->GetSafeHandle(), TRUE);
+		}
+	}
+	// S1F23 QueryAllCEID
+	{
+		CRect rc(14, 152, 14 + 140, 152 + 14); // dialog units, next row
+		MapDialogRect(&rc);
+		HWND hBtn = ::CreateWindow(_T("BUTTON"), _T("S1F23_QueryAllCEID"),
+			WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
+			rc.left, rc.top, rc.Width(), rc.Height(),
+			m_hWnd, (HMENU)IDC_BUTTON_QUERY_ALL_CEID, AfxGetInstanceHandle(), nullptr);
+		if (hBtn != nullptr) {
+			::SendMessage(hBtn, WM_SETFONT, (WPARAM)GetFont()->GetSafeHandle(), TRUE);
+		}
+	}
+	// S1F21 QueryAllDVID
+	{
+		CRect rc(14 + 140 + 5, 152, 14 + 140 + 5 + 140, 152 + 14);
+		MapDialogRect(&rc);
+		HWND hBtn = ::CreateWindow(_T("BUTTON"), _T("S1F21_QueryAllDVID"),
+			WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
+			rc.left, rc.top, rc.Width(), rc.Height(),
+			m_hWnd, (HMENU)IDC_BUTTON_QUERY_ALL_DVID, AfxGetInstanceHandle(), nullptr);
+		if (hBtn != nullptr) {
+			::SendMessage(hBtn, WM_SETFONT, (WPARAM)GetFont()->GetSafeHandle(), TRUE);
+		}
+	}
+	// S2F13 QueryAllECID
+	{
+		CRect rc(14 + 2 * (140 + 5), 192, 14 + 2 * (140 + 5) + 140, 192 + 14);
+		MapDialogRect(&rc);
+		HWND hBtn = ::CreateWindow(_T("BUTTON"), _T("S2F13_QueryAllECID"),
+			WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
+			rc.left, rc.top, rc.Width(), rc.Height(),
+			m_hWnd, (HMENU)IDC_BUTTON_QUERY_ALL_ECID, AfxGetInstanceHandle(), nullptr);
+		if (hBtn != nullptr) {
+			::SendMessage(hBtn, WM_SETFONT, (WPARAM)GetFont()->GetSafeHandle(), TRUE);
+		}
+	}
+	// ECID edit + send (S2F15)
+	{
+		CRect rcEcid(14, 192, 14 + 60, 192 + 14);
+		CRect rcEcv(14 + 60 + 4, 192, 14 + 60 + 4 + 60, 192 + 14);
+		MapDialogRect(&rcEcid);
+		MapDialogRect(&rcEcv);
+		HWND hEditEcid = ::CreateWindow(_T("EDIT"), _T(""), WS_CHILD | WS_VISIBLE | WS_BORDER,
+			rcEcid.left, rcEcid.top, rcEcid.Width(), rcEcid.Height(),
+			m_hWnd, (HMENU)IDC_EDIT_ECID, AfxGetInstanceHandle(), nullptr);
+		HWND hEditEcv = ::CreateWindow(_T("EDIT"), _T(""), WS_CHILD | WS_VISIBLE | WS_BORDER,
+			rcEcv.left, rcEcv.top, rcEcv.Width(), rcEcv.Height(),
+			m_hWnd, (HMENU)IDC_EDIT_ECV, AfxGetInstanceHandle(), nullptr);
+		if (hEditEcid) ::SendMessage(hEditEcid, WM_SETFONT, (WPARAM)GetFont()->GetSafeHandle(), TRUE);
+		if (hEditEcv) ::SendMessage(hEditEcv, WM_SETFONT, (WPARAM)GetFont()->GetSafeHandle(), TRUE);
+
+		CRect rcBtn(14 + 60 + 4 + 60 + 4, 192, 14 + 60 + 4 + 60 + 4 + 90, 192 + 14);
+		MapDialogRect(&rcBtn);
+		HWND hBtn = ::CreateWindow(_T("BUTTON"), _T("S2F15_SetECID"),
+			WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
+			rcBtn.left, rcBtn.top, rcBtn.Width(), rcBtn.Height(),
+			m_hWnd, (HMENU)IDC_BUTTON_SET_ECID, AfxGetInstanceHandle(), nullptr);
+		if (hBtn) ::SendMessage(hBtn, WM_SETFONT, (WPARAM)GetFont()->GetSafeHandle(), TRUE);
+	}
+	// S1F3 CurrentRecipe (EQ specific) combo + button
+	{
+		CRect rcCombo(14, 168, 14 + 120, 168 + 120); // dropdown height arbitrary
+		MapDialogRect(&rcCombo);
+		HWND hCombo = ::CreateWindow(_T("COMBOBOX"), _T(""),
+			WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | WS_VSCROLL,
+			rcCombo.left, rcCombo.top, rcCombo.Width(), rcCombo.Height(),
+			m_hWnd, (HMENU)IDC_COMBO_EQ_FOR_RECIPE, AfxGetInstanceHandle(), nullptr);
+		if (hCombo != nullptr) {
+			::SendMessage(hCombo, WM_SETFONT, (WPARAM)GetFont()->GetSafeHandle(), TRUE);
+			// 绠�鍗曞~鍏呰澶囧垪琛紙鍙寜闇�璋冩暣锛�
+			std::vector<CString> eqs = { _T("Bonder1"), _T("Bonder2"), _T("EFEM"), _T("VacuumBake"), _T("BakeCooling"), _T("Measurement") };
+			for (const auto& eq : eqs) {
+				::SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM)(LPCTSTR)eq);
+			}
+			::SendMessage(hCombo, CB_SETCURSEL, 0, 0);
+		}
+
+		CRect rcBtn(140, 168, 14 + 140 + 118, 168 + 14);
+		MapDialogRect(&rcBtn);
+		HWND hBtn = ::CreateWindow(_T("BUTTON"), _T("S1F3_CurrentRecipe"),
+			WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
+			rcBtn.left, rcBtn.top, rcBtn.Width(), rcBtn.Height(),
+			m_hWnd, (HMENU)IDC_BUTTON_QUERY_CURRENT_RECIPE, AfxGetInstanceHandle(), nullptr);
+		if (hBtn != nullptr) {
+			::SendMessage(hBtn, WM_SETFONT, (WPARAM)GetFont()->GetSafeHandle(), TRUE);
+		}
+	}
 
 	SetDlgItemText(IDC_EDIT_IP, _T("127.0.0.1"));
 	SetDlgItemInt(IDC_EDIT_PORT, 7000);
@@ -282,12 +461,42 @@
 	GetDlgItem(IDC_BUTTON_TRANSMIT_SPOOLED_DATA)->EnableWindow(enabled);
 	GetDlgItem(IDC_BUTTON_PURGE_SPOOLED_DATA)->EnableWindow(enabled);
 	GetDlgItem(IDC_BUTTON_QUERY_PPID_LIST)->EnableWindow(enabled);	
+	if (GetDlgItem(IDC_BUTTON_DELETE_PPID) != nullptr) {
+		GetDlgItem(IDC_BUTTON_DELETE_PPID)->EnableWindow(enabled);
+	}
+	if (GetDlgItem(IDC_EDIT_DELETE_PPID) != nullptr) {
+		GetDlgItem(IDC_EDIT_DELETE_PPID)->EnableWindow(enabled);
+	}
 	GetDlgItem(IDC_BUTTON_PROCEED_WITH_CARRIER)->EnableWindow(enabled);	
+	if (GetDlgItem(IDC_BUTTON_PROCEED_WITH_SLOTMAP) != nullptr) {
+		GetDlgItem(IDC_BUTTON_PROCEED_WITH_SLOTMAP)->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);	
+	if (GetDlgItem(IDC_BUTTON_CTRL_OFFLINE) != nullptr) {
+		GetDlgItem(IDC_BUTTON_CTRL_OFFLINE)->EnableWindow(enabled);
+	}
+	if (GetDlgItem(IDC_BUTTON_CTRL_ONLINE_LOCAL) != nullptr) {
+		GetDlgItem(IDC_BUTTON_CTRL_ONLINE_LOCAL)->EnableWindow(enabled);
+	}
+	if (GetDlgItem(IDC_BUTTON_CTRL_ONLINE_REMOTE) != nullptr) {
+		GetDlgItem(IDC_BUTTON_CTRL_ONLINE_REMOTE)->EnableWindow(enabled);
+	}
+	if (GetDlgItem(IDC_BUTTON_QUERY_CONTROL_STATE) != nullptr) {
+		GetDlgItem(IDC_BUTTON_QUERY_CONTROL_STATE)->EnableWindow(enabled);
+	}
+	if (GetDlgItem(IDC_BUTTON_QUERY_PROCESS_STATE) != nullptr) {
+		GetDlgItem(IDC_BUTTON_QUERY_PROCESS_STATE)->EnableWindow(enabled);
+	}
+	if (GetDlgItem(IDC_BUTTON_QUERY_ALL_SVID) != nullptr) {
+		GetDlgItem(IDC_BUTTON_QUERY_ALL_SVID)->EnableWindow(enabled);
+	}
+	if (GetDlgItem(IDC_BUTTON_QUERY_ALL_CEID) != nullptr) {
+		GetDlgItem(IDC_BUTTON_QUERY_ALL_CEID)->EnableWindow(enabled);
+	}
 }
 
 void CEAPSimulatorDlg::OnBnClickedButtonConnect()
@@ -400,10 +609,48 @@
 	theApp.m_model.m_pHsmsActive->hsmsQueryPPIDList();
 }
 
+void CEAPSimulatorDlg::OnBnClickedButtonDeletePpid()
+{
+	CString strPPID;
+	GetDlgItemText(IDC_EDIT_DELETE_PPID, strPPID);
+	strPPID.Trim();
+	std::vector<std::string> ppids;
+	if (!strPPID.IsEmpty()) {
+		CString upper = strPPID;
+		upper.MakeUpper();
+		if (upper != _T("ALL")) {
+			int start = 0;
+			CString token = strPPID.Tokenize(_T(","), start);
+			while (!token.IsEmpty()) {
+				token.Trim();
+				if (!token.IsEmpty()) {
+					ppids.push_back(std::string((LPTSTR)(LPCTSTR)token));
+				}
+				token = strPPID.Tokenize(_T(","), start);
+			}
+		}
+	}
+	// L:0 if ppids empty -> delete all
+	theApp.m_model.m_pHsmsActive->hsmsDeletePPID(ppids);
+}
+
 static int DATAID = 1;
 void CEAPSimulatorDlg::OnBnClickedButtonProceedWithCarrier()
 {
 	theApp.m_model.m_pHsmsActive->hsmsProceedWithCarrier(DATAID++, "CSX 52078", 1);
+}
+
+void CEAPSimulatorDlg::OnBnClickedButtonProceedWithSlotMap()
+{
+	// Example payload (edit as needed)
+	std::vector<std::string> panelIds = {
+		"PANEL_01","PANEL_02","PANEL_03","PANEL_04",
+		"PANEL_05","PANEL_06","PANEL_07","PANEL_08"
+	};
+	std::vector<unsigned char> slotMap = {
+		3,3,1,1,1,1,1,1 // 1=Empty, 3=Exist
+	};
+	theApp.m_model.m_pHsmsActive->hsmsProceedWithSlotMap(DATAID++, "CSX 52078", 1, "LOT_0001", panelIds, slotMap);
 }
 
 void CEAPSimulatorDlg::OnBnClickedButtonCarrierRelease()
@@ -419,6 +666,106 @@
 
 void CEAPSimulatorDlg::OnBnClickedButtonCreateCj()
 {
-	std::vector<std::string> processJobIds = {"PJ0001", "PJ0003"};
+	std::vector<std::string> processJobIds = {"PJ0001"};
 	theApp.m_model.m_pHsmsActive->hsmsCreateControlJob("CJ5007", processJobIds);
 }
+
+void CEAPSimulatorDlg::OnBnClickedButtonCtrlOffline()
+{
+	theApp.m_model.m_pHsmsActive->hsmsRequestOffline();
+}
+
+void CEAPSimulatorDlg::OnBnClickedButtonCtrlOnlineLocal()
+{
+	theApp.m_model.m_pHsmsActive->hsmsRequestOnline();
+	theApp.m_model.m_pHsmsActive->hsmsGoLocal();
+}
+
+void CEAPSimulatorDlg::OnBnClickedButtonCtrlOnlineRemote()
+{
+	theApp.m_model.m_pHsmsActive->hsmsRequestOnline();
+	theApp.m_model.m_pHsmsActive->hsmsGoRemote();
+}
+
+void CEAPSimulatorDlg::OnBnClickedButtonQueryControlState()
+{
+	theApp.m_model.m_pHsmsActive->hsmsSelectedEquipmentStatusRequest(SVID_ControlState);
+}
+
+void CEAPSimulatorDlg::OnBnClickedButtonQueryProcessState()
+{
+	theApp.m_model.m_pHsmsActive->hsmsSelectedEquipmentStatusRequest(SVID_CurrentProcessState);
+}
+
+void CEAPSimulatorDlg::OnBnClickedButtonQueryAllSvid()
+{
+	theApp.m_model.m_pHsmsActive->hsmsQueryAllStatusVariables();
+}
+
+void CEAPSimulatorDlg::OnBnClickedButtonQueryAllDvid()
+{
+	theApp.m_model.m_pHsmsActive->hsmsQueryAllDataVariables();
+}
+
+void CEAPSimulatorDlg::OnBnClickedButtonQueryAllCeid()
+{
+	theApp.m_model.m_pHsmsActive->hsmsQueryAllCollectionEvents();
+}
+
+void CEAPSimulatorDlg::OnBnClickedButtonQueryAllEcid()
+{
+	// empty list => all ECID
+	std::vector<unsigned short> ecids;
+	ecids.push_back(2000);
+	theApp.m_model.m_pHsmsActive->hsmsEquipmentConstantRequest(ecids);
+}
+
+void CEAPSimulatorDlg::OnBnClickedButtonSetEcid()
+{
+	// simple demo: read ECID and value from edit boxes (reuse PPID edit)
+	CString sEcid, sVal;
+	GetDlgItemText(IDC_EDIT_ECID, sEcid);
+	GetDlgItemText(IDC_EDIT_ECV, sVal);
+	unsigned short id = static_cast<unsigned short>(_ttoi(sEcid));
+	std::string val = CT2A(sVal);
+	std::vector<std::pair<unsigned short, std::string>> kvs;
+	if (id != 0) {
+		kvs.push_back({ id, val });
+		theApp.m_model.m_pHsmsActive->hsmsEquipmentConstantSend(kvs);
+	}
+}
+void CEAPSimulatorDlg::OnBnClickedButtonQueryCurrentRecipe()
+{
+	CString sel;
+	CComboBox* pCombo = (CComboBox*)GetDlgItem(IDC_COMBO_EQ_FOR_RECIPE);
+	if (pCombo != nullptr) {
+		int idx = pCombo->GetCurSel();
+		if (idx != CB_ERR) {
+			pCombo->GetLBText(idx, sel);
+		}
+	}
+	unsigned int svid = SVID_EQPPExecName; // 榛樿鍏ㄥ眬
+	CString upper = sel;
+	upper.MakeUpper();
+	if (upper.Find(_T("BONDER1")) != -1) svid = SVID_Bonder1CurrentRecipe;
+	else if (upper.Find(_T("BONDER2")) != -1) svid = SVID_Bonder2CurrentRecipe;
+	else if (upper.Find(_T("VACUUMBAKE")) != -1) svid = SVID_VacuumBakeCurrentRecipe;
+	else if (upper.Find(_T("BAKECOOLING")) != -1) svid = SVID_BakeCoolingCurrentRecipe;
+	else if (upper.Find(_T("MEASUREMENT")) != -1) svid = SVID_MeasurementCurrentRecipe;
+	else if (upper.Find(_T("EFEM")) != -1) svid = SVID_EFEMCurrentRecipe;
+
+	theApp.m_model.m_pHsmsActive->hsmsSelectedEquipmentStatusRequest(svid);
+}
+
+void CEAPSimulatorDlg::OnBnClickedButtonPpRequest()
+{
+	CString strPPID;
+	GetDlgItemText(IDC_EDIT_PPID_REQ, strPPID);
+	strPPID.Trim();
+	std::string ppid = CT2A(strPPID);
+	if (ppid.empty()) {
+		AfxMessageBox(_T("璇疯緭鍏� PPID"));
+		return;
+	}
+	theApp.m_model.m_pHsmsActive->hsmsProcessProgramRequest(ppid.c_str());
+}
diff --git a/SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.h b/SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.h
index e10cf1a..d1e5537 100644
--- a/SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.h
+++ b/SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.h
@@ -57,10 +57,24 @@
 	afx_msg void OnBnClickedButtonTransmitSpooledData();
 	afx_msg void OnBnClickedButtonPurgeSpooledData();
 	afx_msg void OnBnClickedButtonQueryPpidList();
+	afx_msg void OnBnClickedButtonDeletePpid();
 	afx_msg void OnBnClickedButtonProceedWithCarrier();
+	afx_msg void OnBnClickedButtonProceedWithSlotMap();
 	afx_msg void OnBnClickedButtonCarrierRelease();
 	afx_msg void OnBnClickedButtonQueryCjSpace();
 	afx_msg void OnBnClickedButtonQueryPjSpace();
 	afx_msg void OnBnClickedButtonCreatePj();
 	afx_msg void OnBnClickedButtonCreateCj();
+	afx_msg void OnBnClickedButtonCtrlOffline();
+	afx_msg void OnBnClickedButtonCtrlOnlineLocal();
+	afx_msg void OnBnClickedButtonCtrlOnlineRemote();
+	afx_msg void OnBnClickedButtonQueryControlState();
+	afx_msg void OnBnClickedButtonQueryProcessState();
+	afx_msg void OnBnClickedButtonQueryAllSvid();
+	afx_msg void OnBnClickedButtonQueryAllDvid();
+	afx_msg void OnBnClickedButtonQueryAllCeid();
+	afx_msg void OnBnClickedButtonQueryAllEcid();
+	afx_msg void OnBnClickedButtonSetEcid();
+	afx_msg void OnBnClickedButtonQueryCurrentRecipe();
+	afx_msg void OnBnClickedButtonPpRequest();
 };
diff --git a/SourceCode/Bond/EAPSimulator/Resource.h b/SourceCode/Bond/EAPSimulator/Resource.h
index 96c191e..5c4789f 100644
--- a/SourceCode/Bond/EAPSimulator/Resource.h
+++ b/SourceCode/Bond/EAPSimulator/Resource.h
@@ -57,14 +57,33 @@
 #define IDC_BUTTON_CREATE_PJ2           1040
 #define IDC_BUTTON_CREATE_CJ            1040
 #define IDC_BUTTON_DELETE               1041
+#define IDC_BUTTON_PROCEED_WITH_SLOTMAP 1042
+#define IDC_BUTTON_CTRL_OFFLINE         1043
+#define IDC_BUTTON_CTRL_ONLINE_LOCAL    1044
+#define IDC_BUTTON_CTRL_ONLINE_REMOTE   1045
+#define IDC_BUTTON_QUERY_CONTROL_STATE  1046
+#define IDC_BUTTON_QUERY_PROCESS_STATE  1047
+#define IDC_BUTTON_QUERY_ALL_SVID       1048
+#define IDC_BUTTON_QUERY_ALL_CEID       1049
+#define IDC_EDIT_DELETE_PPID            1050
+#define IDC_BUTTON_DELETE_PPID          1051
+#define IDC_COMBO_EQ_FOR_RECIPE         1052
+#define IDC_BUTTON_QUERY_CURRENT_RECIPE 1053
+#define IDC_EDIT_PPID_REQ               1054
+#define IDC_BUTTON_PP_REQUEST           1055
+#define IDC_BUTTON_QUERY_ALL_DVID       1056
+#define IDC_BUTTON_QUERY_ALL_ECID       1057
+#define IDC_EDIT_ECID                   1058
+#define IDC_EDIT_ECV                    1059
+#define IDC_BUTTON_SET_ECID             1060
 
 // Next default values for new objects
 // 
 #ifdef APSTUDIO_INVOKED
 #ifndef APSTUDIO_READONLY_SYMBOLS
-#define _APS_NEXT_RESOURCE_VALUE        143
+#define _APS_NEXT_RESOURCE_VALUE        146
 #define _APS_NEXT_COMMAND_VALUE         32771
-#define _APS_NEXT_CONTROL_VALUE         1042
+#define _APS_NEXT_CONTROL_VALUE         1057
 #define _APS_NEXT_SYMED_VALUE           101
 #endif
 #endif
diff --git a/SourceCode/Bond/Servo/AccordionWnd.cpp b/SourceCode/Bond/Servo/AccordionWnd.cpp
new file mode 100644
index 0000000..83af762
--- /dev/null
+++ b/SourceCode/Bond/Servo/AccordionWnd.cpp
@@ -0,0 +1,787 @@
+#include "stdafx.h"
+#include "AccordionWnd.h"
+
+
+#define ITEM_HEIGHT				32
+#define ITEM_SPACE				5
+#define TIMER_ID_CHECKHOVER		1
+#define EXPAND_ICON_WIDE		16
+
+#define BORDER			5
+#define SHADOWWIDE		5
+
+CAccordionWnd::CAccordionWnd()
+{
+	m_hWnd = NULL;
+	m_crFrame = GetSysColor(COLOR_WINDOWFRAME);
+	m_crBkgnd = RGB(255, 255, 255);//GetSysColor(COLOR_BTNFACE); ;
+	m_nPadding[PADDING_LEFT] = 5;
+	m_nPadding[PADDING_TOP] = 5;
+	m_nPadding[PADDING_RIGHT] = 5;
+	m_nPadding[PADDING_BOTTOM] = 5;
+	m_crItemBackground[0] = RGB(218, 218, 218);
+	m_crItemBackground[1] = RGB(34, 177, 76);
+	m_crItemFrame[0] = RGB(128, 128, 128);
+	m_crItemFrame[1] = RGB(128, 128, 128);
+	m_crItemText[0] = RGB(68, 84, 111);
+	m_crItemText[1] = RGB(0, 0, 0);
+	m_crSeparateLine = RGB(222, 222, 222);
+	m_crHoverItemBackground = RGB(244, 245, 247);
+	m_crHoverItemFrame = RGB(200, 222, 255);
+	m_hIconExpand = NULL;
+	m_hIconClose = NULL;
+	m_nHoverItem = -1;
+	m_nCheckHoverItem = -1;
+	m_bShadow = FALSE;
+	m_crShadowBkgnd = GetSysColor(COLOR_BTNFACE);
+}
+
+
+CAccordionWnd::~CAccordionWnd()
+{
+	for (size_t i = 0; i < m_vectorItems.size(); i++) {
+		delete m_vectorItems[i];
+	}
+	m_vectorItems.clear();
+}
+
+BOOL CAccordionWnd::RegisterWndClass()
+{
+	WNDCLASS wcExisting = {};
+	HINSTANCE hInstance = AfxGetInstanceHandle();
+	if (::GetClassInfo(hInstance, ACCORDIONWND_CLASS, &wcExisting) ||
+		::GetClassInfo(NULL, ACCORDIONWND_CLASS, &wcExisting)) {
+		return TRUE;
+	}
+
+	WNDCLASS wc = {};
+	wc.lpszClassName = ACCORDIONWND_CLASS;
+	wc.hInstance = hInstance;
+	wc.lpfnWndProc = WindowProc;
+	wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
+	wc.hIcon = 0;
+	wc.lpszMenuName = NULL;
+	wc.hbrBackground = NULL;
+	wc.style = CS_GLOBALCLASS | CS_DBLCLKS;
+	wc.cbClsExtra = 0;
+	wc.cbWndExtra = 0;
+
+	// 注册自定义类
+	if (::RegisterClass(&wc) != 0) {
+		return TRUE;
+	}
+	return (::GetLastError() == ERROR_CLASS_ALREADY_EXISTS);
+}
+
+CAccordionWnd* CAccordionWnd::FromHandle(HWND hWnd)
+{
+	CAccordionWnd* pAccordionWnd = (CAccordionWnd*)::GetProp(hWnd, ACCORDIONWND_TAG);
+	return pAccordionWnd;
+}
+
+CAccordionWnd* CAccordionWnd::Hook(HWND hWnd)
+{
+	CAccordionWnd* pAccordionWnd = (CAccordionWnd*)GetProp(hWnd, ACCORDIONWND_TAG);
+	if (pAccordionWnd == NULL) {
+		pAccordionWnd = new CAccordionWnd();
+		pAccordionWnd->m_hWnd = hWnd;
+
+		SetProp(hWnd, ACCORDIONWND_TAG, (HANDLE)pAccordionWnd);
+	}
+
+
+	return pAccordionWnd;
+}
+
+void CAccordionWnd::LoadExpandIcon(CString strExpandFile, CString strCloseFile)
+{
+	m_hIconExpand = (HICON)::LoadImage(AfxGetInstanceHandle(), strExpandFile, IMAGE_ICON, EXPAND_ICON_WIDE, EXPAND_ICON_WIDE,
+		LR_LOADFROMFILE | LR_DEFAULTCOLOR | LR_CREATEDIBSECTION | LR_DEFAULTSIZE);
+	m_hIconClose = (HICON)::LoadImage(AfxGetInstanceHandle(), strCloseFile, IMAGE_ICON, EXPAND_ICON_WIDE, EXPAND_ICON_WIDE,
+		LR_LOADFROMFILE | LR_DEFAULTCOLOR | LR_CREATEDIBSECTION | LR_DEFAULTSIZE);
+}
+
+void CAccordionWnd::Setpadding(int type, unsigned int nPadding)
+{
+	if (type >= PADDING_LEFT && PADDING_LEFT <= PADDING_BOTTOM) {
+		m_nPadding[type] = nPadding;
+	}
+}
+
+void CAccordionWnd::SetDefaultItemBackgroundColor(COLORREF crNormal, COLORREF crSel)
+{
+	m_crItemBackground[0] = crNormal;
+	m_crItemBackground[1] = crSel;
+}
+
+void CAccordionWnd::SetDefaultItemFrameColor(COLORREF crNormal, COLORREF crSel)
+{
+	m_crItemFrame[0] = crNormal;
+	m_crItemFrame[1] = crSel;
+}
+
+void CAccordionWnd::SetDefaultItemTextColor(COLORREF crNormal, COLORREF crSel)
+{
+	m_crItemText[0] = crNormal;
+	m_crItemText[1] = crSel;
+}
+
+void CAccordionWnd::Init()
+{
+}
+
+void CAccordionWnd::Release()
+{
+	// delete
+	delete this;
+}
+
+/*
+ * 添加项目
+ * pszName -- 名称
+ * pWnd -- 绑定的窗口
+ * nExpandHeight -- 展开高度,如果为0则自动设置为窗口高
+ */
+void CAccordionWnd::AddItem(char* pszName, CWnd* pWnd, int nExpandHeight, BOOL bExpand/* = TRUE*/, BOOL bEnable/* = TRUE*/)
+{
+	ACCORDIONITEM* pItem = new ACCORDIONITEM;
+	memset(pItem, 0, sizeof(ACCORDIONITEM));
+	pItem->pWnd = pWnd;
+	pItem->bExpand = bExpand;
+	pItem->bEnable = bEnable;
+	strcpy_s(pItem->text, sizeof(pItem->text), pszName);
+	if (nExpandHeight == 0) {
+		RECT rect;
+		pWnd->GetWindowRect(&rect);
+		pItem->nExpandHeight = rect.bottom - rect.top;
+	}
+	else if (nExpandHeight == -1) {
+		pItem->nExpandHeight = -1;
+	}
+	else {
+		pItem->nExpandHeight = nExpandHeight;
+	}
+	m_vectorItems.push_back(pItem);
+
+
+	// 重新调整个子窗口的位置
+	ResizeItemWnd();
+}
+
+void CAccordionWnd::ResizeItemWnd()
+{
+	RECT rcClient, rcItemClient;
+	GetClientRect(m_hWnd, &rcClient);
+
+	for (size_t i = 0; i < m_vectorItems.size(); i++) {
+		ACCORDIONITEM* pItem = m_vectorItems.at(i);
+		if (pItem->pWnd != NULL) {
+			GetItemRect(rcClient, (UINT)i, &rcItemClient);
+			rcItemClient.top += ITEM_HEIGHT;
+			if (pItem->nExpandHeight == -1) {
+				rcItemClient.bottom = rcClient.bottom;
+			}
+			else {
+				rcItemClient.bottom = rcItemClient.top + pItem->nExpandHeight;
+			}
+
+			pItem->pWnd->MoveWindow(&rcItemClient);
+			pItem->pWnd->ShowWindow(pItem->bExpand ? SW_SHOW : SW_HIDE);
+		}
+	}
+}
+
+BOOL CAccordionWnd::GetItemHeaderRect(RECT rcClient, unsigned int nIndex, LPRECT lpRect)
+{
+	RECT rcItem;
+	if (!GetItemRect(rcClient, nIndex, &rcItem)) {
+		return FALSE;
+	}
+
+	rcItem.bottom = rcItem.top + ITEM_HEIGHT;
+	CopyRect(lpRect, &rcItem);
+	return TRUE;
+}
+
+BOOL CAccordionWnd::GetItemRect(RECT rcClient, unsigned int nIndex, LPRECT lpRect)
+{
+	if (nIndex >= m_vectorItems.size()) {
+		return FALSE;
+	}
+
+	RECT rcItemHeader;
+	rcItemHeader.left = rcClient.left + m_nPadding[PADDING_LEFT];
+	rcItemHeader.right = rcClient.right - m_nPadding[PADDING_RIGHT];
+	rcItemHeader.top = rcClient.top + m_nPadding[PADDING_TOP];
+	rcItemHeader.bottom = rcItemHeader.top + ITEM_HEIGHT;
+	for (size_t i = 0; i < m_vectorItems.size(); i++) {
+		ACCORDIONITEM* pItem = m_vectorItems.at(i);
+		if (pItem->bExpand) {
+			rcItemHeader.bottom += pItem->nExpandHeight;
+		}
+
+		if (i == nIndex) {
+			break;;
+		}
+
+		rcItemHeader.top = rcItemHeader.bottom + ITEM_SPACE;
+		rcItemHeader.bottom = rcItemHeader.top + ITEM_HEIGHT;
+	}
+
+
+	CopyRect(lpRect, &rcItemHeader);
+	return TRUE;
+}
+
+int CAccordionWnd::HitTest(POINT pt, int& nHitTest)
+{
+	int nRet = -1;
+	nHitTest = -1;
+	RECT rcClient;
+	GetClientRect(m_hWnd, &rcClient);
+	if (PtInRect(&rcClient, pt)) {
+		nRet = 1;
+	}
+
+	int nItemIndex = -1;
+	RECT rcItemHeader;
+	for (size_t i = 0; i < m_vectorItems.size(); i++) {
+		GetItemHeaderRect(rcClient, (unsigned int)i, &rcItemHeader);
+
+		if (PtInRect(&rcItemHeader, pt)) {
+			nItemIndex = (unsigned int)i;
+
+			break;
+		}
+	}
+
+	if (nItemIndex != -1) {
+		nRet = 2;
+		nHitTest = nItemIndex;
+	}
+
+	return nRet;
+}
+
+BOOL CAccordionWnd::Togle(unsigned int nIndex)
+{
+	if (nIndex >= m_vectorItems.size()) {
+		return FALSE;
+	}
+
+	ACCORDIONITEM* pItem = m_vectorItems[nIndex];
+	pItem->bExpand = !pItem->bExpand;
+
+
+	// 重新调整个子窗口的位置
+	ResizeItemWnd();
+
+	RECT rcClient;
+	GetClientRect(m_hWnd, &rcClient);
+	::InvalidateRect(m_hWnd, &rcClient, TRUE);
+
+	return TRUE;
+}
+
+void CAccordionWnd::Notify(int nCode, int dwData, int dwData1/* = 0*/, int dwData2/* = 0*/)
+{
+	HWND hParent;
+	hParent = GetParent(m_hWnd);
+	if (hParent != NULL) {
+		ACCORDION_NMHDR accordionWndnmhdr;
+		accordionWndnmhdr.nmhdr.hwndFrom = m_hWnd;
+		accordionWndnmhdr.nmhdr.idFrom = GetWindowLong(m_hWnd, GWL_ID);
+		accordionWndnmhdr.nmhdr.code = nCode;
+		accordionWndnmhdr.dwData = dwData;
+		accordionWndnmhdr.dwData1 = dwData1;
+		accordionWndnmhdr.dwData2 = dwData2;
+		SendMessage(hParent, WM_NOTIFY, (WPARAM)accordionWndnmhdr.nmhdr.idFrom, (LPARAM)&accordionWndnmhdr);
+	}
+}
+
+/*
+ * 拦截窗口消息函数
+ */
+LRESULT CALLBACK CAccordionWnd::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+	CAccordionWnd* pAccordionWnd = (CAccordionWnd*)GetProp(hWnd, ACCORDIONWND_TAG);
+	if (pAccordionWnd == NULL && uMsg != WM_NCCREATE)
+	{
+		return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
+	}
+
+
+	// 如果Hook则响应消息
+	ASSERT(hWnd);
+	switch (uMsg)
+	{
+	case WM_NCCREATE:
+		return CAccordionWnd::OnNcCreate(hWnd, wParam, lParam);
+
+	case WM_DESTROY:
+		return pAccordionWnd->OnDestroy(wParam, lParam);
+
+	case WM_NCCALCSIZE:
+		return pAccordionWnd->OnNcCalcsize(wParam, lParam);
+
+	case WM_NCPAINT:
+		return pAccordionWnd->OnNcPaint(wParam, lParam);
+
+	case WM_PAINT:
+		return pAccordionWnd->OnPaint(wParam, lParam);
+
+	case WM_TIMER:
+		return pAccordionWnd->OnTimer(wParam, lParam);
+
+	case WM_MOUSEMOVE:
+		return pAccordionWnd->OnMouseMove(wParam, lParam);
+
+	case WM_LBUTTONDOWN:
+		return pAccordionWnd->OnLButtonDown(wParam, lParam);
+
+	case WM_LBUTTONUP:
+		return pAccordionWnd->OnLButtonUp(wParam, lParam);
+
+	case WM_MOUSEWHEEL:
+		return pAccordionWnd->OnMouseWheel(wParam, lParam);
+
+	case WM_SIZE:
+		return pAccordionWnd->OnSize(wParam, lParam);
+
+	case WM_GETDLGCODE:
+		return DLGC_WANTALLKEYS;
+
+	default:
+		break;
+	}
+
+	return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
+}
+
+/*
+ * WM_NCCREATE
+ * 窗口创建前的初始化工作
+ */
+LRESULT CAccordionWnd::OnNcCreate(HWND hWnd, WPARAM wParam, LPARAM lParam)
+{
+	CAccordionWnd* pAccordionWnd = (CAccordionWnd*)GetProp(hWnd, ACCORDIONWND_TAG);
+	ASSERT(pAccordionWnd == NULL);
+
+	Hook(hWnd);
+	return ::DefWindowProc(hWnd, WM_NCCREATE, wParam, lParam);
+}
+
+/*
+ * WM_NCCALCSIZE
+ */
+LRESULT CAccordionWnd::OnNcCalcsize(WPARAM wParam, LPARAM lParam)
+{
+	if (!m_bShadow) {
+		return ::DefWindowProc(m_hWnd, WM_NCCALCSIZE, wParam, lParam);
+	}
+
+
+	LPRECT lprcWnd = (LPRECT)lParam;
+	lprcWnd->left += BORDER;
+	lprcWnd->top += BORDER;
+	lprcWnd->right -= (BORDER + SHADOWWIDE);
+	lprcWnd->bottom -= (BORDER + SHADOWWIDE);
+
+	return 0;
+}
+
+/*
+ * WM_DESTROY
+ * 窗口销毁时
+ */
+LRESULT CAccordionWnd::OnDestroy(WPARAM wParam, LPARAM lParam)
+{
+	Release();
+	return ::DefWindowProc(m_hWnd, WM_DESTROY, wParam, lParam);
+}
+
+
+/*
+ * WM_TIMER
+ */
+LRESULT CAccordionWnd::OnTimer(WPARAM wParam, LPARAM lParam)
+{
+	int nTimerId = (int)wParam;
+	if (m_nTimerId == nTimerId) {
+
+		POINT pt;
+		::GetCursorPos(&pt);
+		::ScreenToClient(m_hWnd, &pt);
+
+		int nRet, nHitTest;
+		nRet = HitTest(pt, nHitTest);
+		if (m_nCheckHoverItem != nHitTest) {
+			KillTimer(m_hWnd, m_nTimerId);
+			m_nHoverItem = nHitTest;
+			m_nCheckHoverItem = nHitTest;
+
+			RECT rcClient;
+			GetClientRect(m_hWnd, &rcClient);
+			::InvalidateRect(m_hWnd, &rcClient, TRUE);
+		}
+	}
+
+
+	return ::DefWindowProc(m_hWnd, WM_TIMER, wParam, lParam);
+}
+
+/*
+ * WM_MOUSEMOVE
+ * 鼠标移动时,检测鼠标位置并回调给主窗口
+ */
+LRESULT CAccordionWnd::OnMouseMove(WPARAM wParam, LPARAM lParam)
+{
+	POINT pt;
+	pt.x = (int)LOWORD(lParam);
+	pt.y = (int)HIWORD(lParam);
+
+	int nRet, nHitTest;
+	nRet = HitTest(pt, nHitTest);
+
+	if (nRet == 2) {
+		ACCORDIONITEM* pItem = m_vectorItems[nHitTest];
+		if (pItem != NULL && pItem->bEnable) {
+			::SetCursor(LoadCursor(NULL, IDC_HAND));
+		}
+	}
+	else {
+		::SetCursor(LoadCursor(NULL, IDC_ARROW));
+	}
+
+	int nLastItem = m_nHoverItem;
+	if (m_nHoverItem != nHitTest) {
+		m_nHoverItem = nHitTest;
+
+		RECT rcClient, rcLastItemClient, rcCurItemClient;
+		GetClientRect(m_hWnd, &rcClient);
+		GetItemRect(rcClient, nLastItem, &rcLastItemClient);
+		::InvalidateRect(m_hWnd, &rcLastItemClient, nHitTest < 0);
+
+		if (nHitTest >= 0) {
+			ACCORDIONITEM* pItem = m_vectorItems.at(nHitTest);
+			if (!pItem->bEnable) {
+				m_nHoverItem = -1;
+			}
+			else {
+				KillTimer(m_hWnd, m_nTimerId);
+				m_nTimerId = SetTimer(m_hWnd, TIMER_ID_CHECKHOVER, 200, NULL);
+				m_nCheckHoverItem = m_nHoverItem;
+
+				GetItemRect(rcClient, m_nHoverItem, &rcCurItemClient);
+				::InvalidateRect(m_hWnd, &rcCurItemClient, TRUE);
+			}
+		}
+	}
+
+
+	return ::DefWindowProc(m_hWnd, WM_MOUSEMOVE, wParam, lParam);
+}
+
+/*
+ * WM_LBUTTONDOWN
+ * 鼠标左键下压
+ */
+LRESULT CAccordionWnd::OnLButtonDown(WPARAM wParam, LPARAM lParam)
+{
+	POINT pt;
+	pt.x = (int)LOWORD(lParam);
+	pt.y = (int)HIWORD(lParam);
+
+	int nRet, nHitTest;
+	nRet = HitTest(pt, nHitTest);
+
+	if (nRet == 2) {
+		ACCORDIONITEM* pItem = m_vectorItems[nHitTest];
+		if (pItem != NULL && pItem->bEnable) {
+			::SetCursor(LoadCursor(NULL, IDC_HAND));
+		}
+	}
+	else {
+		::SetCursor(LoadCursor(NULL, IDC_ARROW));
+	}
+
+
+	return ::DefWindowProc(m_hWnd, WM_LBUTTONDOWN, wParam, lParam);
+}
+
+/*
+ * WM_LBUTTONUP
+ * 鼠标左键释放
+ */
+LRESULT CAccordionWnd::OnLButtonUp(WPARAM wParam, LPARAM lParam)
+{
+	POINT pt;
+	pt.x = (int)LOWORD(lParam);
+	pt.y = (int)HIWORD(lParam);
+
+	int nRet, nHitTest;
+	nRet = HitTest(pt, nHitTest);
+
+	if (nRet == 2) {
+		ACCORDIONITEM* pItem = m_vectorItems[nHitTest];
+		if (pItem != NULL && pItem->bEnable) {
+			::SetCursor(LoadCursor(NULL, IDC_HAND));
+			Togle(nHitTest);
+		}
+	}
+	else {
+		::SetCursor(LoadCursor(NULL, IDC_ARROW));
+	}
+
+
+	return ::DefWindowProc(m_hWnd, WM_LBUTTONUP, wParam, lParam);
+}
+
+/*
+ * WM_MOUSEWHEEL
+ * 鼠标滚轮滚动时,缩放图像
+ */
+LRESULT CAccordionWnd::OnMouseWheel(WPARAM wParam, LPARAM lParam)
+{
+	return ::DefWindowProc(m_hWnd, WM_MOUSEWHEEL, wParam, lParam);
+}
+
+/*
+ * WM_NCPAINT
+ */
+LRESULT CAccordionWnd::OnNcPaint(WPARAM wParam, LPARAM lParam)
+{
+	LRESULT lRet = ::DefWindowProc(m_hWnd, WM_NCPAINT, wParam, lParam);
+
+
+	// 然后画边框
+	long styleEx = GetWindowLong(m_hWnd, GWL_EXSTYLE);
+	if ((styleEx & WS_EX_CLIENTEDGE) == WS_EX_CLIENTEDGE) {
+
+		RECT rcWindow, rcClient;
+		GetClientRect(m_hWnd, &rcClient);
+		::ClientToScreen(m_hWnd, (LPPOINT)&rcClient.left);
+		::ClientToScreen(m_hWnd, (LPPOINT)&rcClient.right);
+		GetWindowRect(m_hWnd, &rcWindow);
+		::OffsetRect(&rcClient, -rcWindow.left, -rcWindow.top);
+		::OffsetRect(&rcWindow, -rcWindow.left, -rcWindow.top);
+
+		HRGN hRgnWnd = CreateRectRgnIndirect(&rcWindow);
+		HRGN hRgnClient = CreateRectRgnIndirect(&rcClient);
+
+		HDC hDC = GetWindowDC(m_hWnd);
+		::SelectClipRgn(hDC, hRgnWnd);
+		::ExtSelectClipRgn(hDC, hRgnClient, RGN_DIFF);
+
+
+		// 没有阴影的边框
+		if (!m_bShadow) {
+			HBRUSH hBrushBK, hBrushFrame;
+			hBrushBK = CreateSolidBrush(m_crBkgnd);
+			::FillRect(hDC, &rcWindow, hBrushBK);
+			DeleteObject(hBrushBK);
+
+			hBrushFrame = CreateSolidBrush(m_crFrame);
+			::FrameRect(hDC, &rcWindow, hBrushFrame);
+			DeleteObject(hBrushFrame);
+		}
+
+		// 有阴影的边框
+		else {
+
+			RECT rcFrame0, rcFrame1;
+			rcFrame0.left = rcWindow.left + SHADOWWIDE;
+			rcFrame0.top = rcWindow.top + SHADOWWIDE;
+			rcFrame0.right = rcWindow.right;
+			rcFrame0.bottom = rcWindow.bottom;
+			rcFrame1.left = rcWindow.left;
+			rcFrame1.top = rcWindow.top;
+			rcFrame1.right = rcWindow.right - SHADOWWIDE;
+			rcFrame1.bottom = rcWindow.bottom - SHADOWWIDE;
+
+
+			// 背景框和对话框(父窗体)背景色一致
+			HBRUSH hBrushBK, hBrushFrame;
+			hBrushBK = CreateSolidBrush(m_crShadowBkgnd);
+			::FillRect(hDC, &rcWindow, hBrushBK);
+			DeleteObject(hBrushBK);
+
+
+			// 阴影框
+			BYTE r = GetRValue(m_crShadowBkgnd);
+			BYTE g = GetGValue(m_crShadowBkgnd);
+			BYTE b = GetBValue(m_crShadowBkgnd);
+			BYTE rstep = (r - GetRValue(m_crFrame)) / SHADOWWIDE;
+			BYTE gstep = (r - GetGValue(m_crFrame)) / SHADOWWIDE;
+			BYTE bstep = (r - GetBValue(m_crFrame)) / SHADOWWIDE;
+
+			for (int i = 0; i < SHADOWWIDE; i++) {
+				hBrushBK = CreateSolidBrush(RGB(r - i * rstep, g - i * gstep, b - i * bstep));
+				::FillRect(hDC, &rcFrame0, hBrushBK);
+				DeleteObject(hBrushBK);
+				rcFrame0.bottom -= 1;
+				rcFrame0.right -= 1;
+			}
+
+
+			// 前景框
+			hBrushBK = CreateSolidBrush(m_crBkgnd);
+			::FillRect(hDC, &rcFrame1, hBrushBK);
+			DeleteObject(hBrushBK);
+
+			hBrushFrame = CreateSolidBrush(m_crFrame);
+			::FrameRect(hDC, &rcFrame1, hBrushFrame);
+			DeleteObject(hBrushFrame);
+		}
+
+
+		::DeleteObject(hRgnWnd);
+		::DeleteObject(hRgnClient);
+		ReleaseDC(m_hWnd, hDC);
+	}
+
+	return lRet;
+}
+
+/*
+ * WM_PAINT
+ */
+LRESULT CAccordionWnd::OnPaint(WPARAM wParam, LPARAM lParam)
+{
+	HDC hDC, hMemDC;
+	HBITMAP hBitmap;
+	RECT rcClient;
+	CString strText;
+	HFONT hFont;
+	HBRUSH hBrushBK;
+
+
+	// BeginPaint
+	PAINTSTRUCT ps;
+	hDC = BeginPaint(m_hWnd, &ps);
+	GetClientRect(m_hWnd, &rcClient);
+
+	hMemDC = ::CreateCompatibleDC(hDC);
+	hBitmap = ::CreateCompatibleBitmap(hDC, rcClient.right - rcClient.left,
+		rcClient.bottom - rcClient.top);
+	::SelectObject(hMemDC, hBitmap);
+	::SetBkMode(hMemDC, TRANSPARENT);
+
+
+	// 背景颜色
+	hBrushBK = CreateSolidBrush(m_crBkgnd);
+	::FillRect(hMemDC, &rcClient, hBrushBK);
+	DeleteObject(hBrushBK);
+
+
+	// 绘子项列表
+	hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
+	::SelectObject(hMemDC, hFont);
+
+	HPEN hPenSeparate = ::CreatePen(PS_SOLID, 1, m_crSeparateLine);
+	RECT rcItem, rcItemHeader;
+	for (size_t i = 0; i < m_vectorItems.size(); i++) {
+		ACCORDIONITEM* pItem = m_vectorItems[i];
+		GetItemRect(rcClient, (UINT)i, &rcItem);
+		GetItemHeaderRect(rcClient, (UINT)i, &rcItemHeader);
+
+
+		// 热点项的背景色和边框
+		if (m_nHoverItem == (int)i) {
+			HBRUSH hbrItemHeaderBackground = CreateSolidBrush(m_crHoverItemBackground);
+			HBRUSH hbrItemFrame = CreateSolidBrush(m_crHoverItemFrame);
+
+			HRGN hRgn = CreateRoundRectRgn(rcItemHeader.left, rcItemHeader.top, rcItemHeader.right, rcItemHeader.bottom, 2, 2);
+			::FillRgn(hMemDC, hRgn, hbrItemHeaderBackground);
+			::FrameRgn(hMemDC, hRgn, hbrItemFrame, 1, 1);
+			::DeleteObject(hbrItemHeaderBackground);
+			::DeleteObject(hbrItemFrame);
+			::DeleteObject(hRgn);
+		}
+
+
+		// 箭头
+		BOOL bDrawIcon = DrawIconEx(hMemDC, rcItemHeader.left + (ITEM_HEIGHT - EXPAND_ICON_WIDE) / 2, rcItemHeader.top + (ITEM_HEIGHT - EXPAND_ICON_WIDE) / 2,
+			pItem->bExpand ? m_hIconExpand : m_hIconClose, EXPAND_ICON_WIDE, EXPAND_ICON_WIDE, 0, 0, DI_NORMAL);
+
+
+		// 文本
+		::SetTextColor(hMemDC, m_nHoverItem == (int)i ? m_crItemText[1] : m_crItemText[0]);
+		RECT rcText;
+		rcText.left = rcItemHeader.left + (bDrawIcon ? ITEM_HEIGHT : (ITEM_HEIGHT - EXPAND_ICON_WIDE) / 2);
+		rcText.top = rcItemHeader.top;
+		rcText.right = rcItemHeader.right;
+		rcText.bottom = rcItemHeader.bottom;
+		::DrawText(hMemDC, pItem->text, (int)strlen(pItem->text), &rcText, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
+
+
+		// 文本右边分隔线
+		SIZE sizeText;
+		GetTextExtentPoint32(hMemDC, pItem->text, (int)strlen(pItem->text), &sizeText);
+
+
+		HPEN hOldPen = (HPEN)::SelectObject(hMemDC, hPenSeparate);
+		MoveToEx(hMemDC, rcText.left + sizeText.cx + 10, rcItemHeader.top + (rcItemHeader.bottom - rcItemHeader.top - 1) / 2, NULL);
+		LineTo(hMemDC, rcItemHeader.right - 10, rcItemHeader.top + (rcItemHeader.bottom - rcItemHeader.top - 1) / 2);
+		::SelectObject(hMemDC, hOldPen);
+
+
+		rcItemHeader.top = rcItemHeader.bottom + ITEM_SPACE;
+		rcItemHeader.bottom = rcItemHeader.top + ITEM_HEIGHT;
+	}
+	::DeleteObject(hPenSeparate);
+
+
+	// EndPaint
+	::BitBlt(hDC, 0, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top,
+		hMemDC, 0, 0, SRCCOPY);
+	EndPaint(m_hWnd, &ps);
+	::DeleteObject(hBitmap);
+	::DeleteDC(hMemDC);
+
+
+	return 1;
+}
+
+/*
+ * WM_SIZE
+ */
+LRESULT CAccordionWnd::OnSize(WPARAM wParam, LPARAM lParam)
+{
+	LRESULT lRet = ::DefWindowProc(m_hWnd, WM_SIZE, wParam, lParam);
+
+	ResizeItemWnd();
+
+	return lRet;
+}
+
+/*
+ * 设置背景色
+ * color -- 背景色
+ */
+void CAccordionWnd::SetBkgndColor(COLORREF color)
+{
+	m_crBkgnd = color;
+}
+
+
+/*
+ * 设置阴影的背景色(即父窗口的背景)
+ * color -- 背景色
+ */
+void CAccordionWnd::SetShadowBkgnd(COLORREF color)
+{
+	m_crShadowBkgnd = color;
+}
+
+/*
+ * 设置边框颜色
+ * color -- 边框颜色
+ */
+void CAccordionWnd::SetFrameColor(COLORREF color, BOOL bShadow/* = FALSE*/)
+{
+	m_crFrame = color;
+	m_bShadow = bShadow;
+	SetWindowPos(m_hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
+}
+
diff --git a/SourceCode/Bond/Servo/AccordionWnd.h b/SourceCode/Bond/Servo/AccordionWnd.h
new file mode 100644
index 0000000..46fa7a1
--- /dev/null
+++ b/SourceCode/Bond/Servo/AccordionWnd.h
@@ -0,0 +1,131 @@
+#pragma once
+#include <functional>
+#include <vector>
+
+#ifndef ACCORDIONWND_TAG
+
+#ifdef _WIN32
+
+#define ACCORDIONWND_CLASSA		"AccordionWnd"
+#define ACCORDIONWND_CLASSW		L"AccordionWnd"
+
+#ifdef UNICODE
+#define ACCORDIONWND_CLASS		ACCORDIONWND_CLASSW
+#else
+#define ACCORDIONWND_CLASS		ACCORDIONWND_CLASSA
+#endif
+
+#else
+#define ACCORDIONWND_CLASS      "AccordionWnd"
+#endif
+
+
+#define ACCORDIONWND_TAG		_T("ACCORDIONWND_TAG")
+
+#define ACCORDIONWND_FIRST		(0U-3590U)
+#define ACCORDIONWND_LAST		(0U-5350U)
+#define ACCORDIONWND_ONTOGLE	(ACCORDIONWND_FIRST - 1)
+
+typedef struct tagACCORDION_NMHDR
+{
+	NMHDR		nmhdr;
+	DWORD		dwData;
+	DWORD		dwData1;
+	DWORD		dwData2;
+} ACCORDION_NMHDR;
+
+typedef struct tagACCORDIONITEM
+{
+	unsigned int id;
+	int nExpandHeight;
+	COLORREF crBackground[2];
+	COLORREF crFrame[2];
+	COLORREF crText[2];
+	char text[256];
+	CWnd *pWnd;
+	BOOL bExpand;
+	BOOL bEnable;				// 是否可以点击展开和收起
+} ACCORDIONITEM;
+
+#endif
+
+#define PADDING_LEFT		0
+#define PADDING_TOP			1
+#define PADDING_RIGHT		2
+#define PADDING_BOTTOM		3
+
+class CAccordionWnd
+{
+public:
+	CAccordionWnd();
+	~CAccordionWnd();
+
+
+public:
+	static BOOL RegisterWndClass();
+	static CAccordionWnd * FromHandle(HWND hWnd);
+	void SetFrameColor(COLORREF color, BOOL bShadow = FALSE);
+	void SetBkgndColor(COLORREF color);
+	void SetShadowBkgnd(COLORREF color);
+
+public:
+	void LoadExpandIcon(CString strExpandFile, CString strCloseFile);
+	void Setpadding(int type, unsigned int nPadding);
+	void SetDefaultItemBackgroundColor(COLORREF crNormal, COLORREF crSel);
+	void SetDefaultItemFrameColor(COLORREF crNormal, COLORREF crSel);
+	void SetDefaultItemTextColor(COLORREF crNormal, COLORREF crSel);
+	void AddItem(char *pszName, CWnd *pWnd, int nExpandHeight, BOOL bExpand = TRUE, BOOL bEnable = TRUE);
+	BOOL Togle(unsigned int nIndex);
+	int GetItemHeaderHeight();
+	BOOL IsExpand(unsigned int nIndex);
+
+private:
+	void Init();
+	void Notify(int nCode, int dwData, int dwData1 = 0, int dwData2 = 0);
+	void Release();
+	void ResizeItemWnd();
+	static CAccordionWnd* Hook(HWND hWnd);
+	static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+	static LRESULT OnNcCreate(HWND hWnd, WPARAM wParam, LPARAM lParam);
+	LRESULT OnDestroy(WPARAM wParam, LPARAM lParam);
+	LRESULT OnTimer(WPARAM wParam, LPARAM lParam);
+	LRESULT OnNcPaint(WPARAM wParam, LPARAM lParam);
+	LRESULT OnNcCalcsize(WPARAM wParam, LPARAM lParam);
+	LRESULT OnPaint(WPARAM wParam, LPARAM lParam);
+	LRESULT OnMouseMove(WPARAM wParam, LPARAM lParam);
+	LRESULT OnLButtonDown(WPARAM wParam, LPARAM lParam);
+	LRESULT OnLButtonUp(WPARAM wParam, LPARAM lParam);
+	LRESULT OnMouseWheel(WPARAM wParam, LPARAM lParam);
+	LRESULT OnSize(WPARAM wParam, LPARAM lParam);
+
+private:
+	HWND		m_hWnd;
+	COLORREF	m_crBkgnd;
+	COLORREF	m_crFrame;
+	HICON		m_hIconClose;
+	HICON		m_hIconExpand;
+	int			m_nHoverItem;
+	int			m_nCheckHoverItem;
+	BOOL		m_bShadow;				// 阴影
+	COLORREF	m_crShadowBkgnd;		// 阴影背景色(即父窗口的颜色)
+
+private:
+	unsigned int m_nPadding[4];
+	COLORREF m_crItemBackground[2];
+	COLORREF m_crItemFrame[2];
+	COLORREF m_crItemText[2];
+	COLORREF m_crSeparateLine;
+	COLORREF m_crHoverItemBackground;
+	COLORREF m_crHoverItemFrame;
+	CString m_strExpandIconFilepath[2];
+
+private:
+	std::vector<ACCORDIONITEM *> m_vectorItems;
+	int m_nTimerId;
+
+private:
+	int HitTest(POINT pt, int &nHitTest);
+	BOOL GetItemHeaderRect(RECT rcClient, unsigned int nIndex, LPRECT lpRect);
+	BOOL GetItemRect(RECT rcClient, unsigned int nIndex, LPRECT lpRect);
+};
+
diff --git a/SourceCode/Bond/Servo/AlarmManager.cpp b/SourceCode/Bond/Servo/AlarmManager.cpp
index 56d6cbb..8cd9acb 100644
--- a/SourceCode/Bond/Servo/AlarmManager.cpp
+++ b/SourceCode/Bond/Servo/AlarmManager.cpp
@@ -1,6 +1,7 @@
-#include "stdafx.h"
+锘�#include "stdafx.h"
 #include "Common.h"
 #include "AlarmManager.h"
+#include "Log.h"
 #include <sstream>
 #include <fstream>
 #include <iostream>
@@ -8,25 +9,26 @@
 #include <ctime>
 #include <iomanip>
 #include <random>
+#include <chrono>
 
-// 常量
+// 甯搁噺
 const std::string DATABASE_FILE = R"(AlarmManager.db)";
 
-// 静态成员初始化
+// 闈欐�佹垚鍛樺垵濮嬪寲
 std::mutex AlarmManager::m_mutex;
 
-// 获取单例实例
+// 鑾峰彇鍗曚緥瀹炰緥
 AlarmManager& AlarmManager::getInstance() {
     static AlarmManager instance;
     return instance;
 }
 
-// 构造函数
+// 鏋勯�犲嚱鏁�
 AlarmManager::AlarmManager() {
 	m_pDB = new BL::SQLiteDatabase();
 }
 
-// 析构函数
+// 鏋愭瀯鍑芥暟
 AlarmManager::~AlarmManager() {
     if (m_pDB != nullptr) {
         delete m_pDB;
@@ -34,7 +36,7 @@
     }
 }
 
-// 初始化报警表
+// 鍒濆鍖栨姤璀﹁〃
 bool AlarmManager::initAlarmTable() {
     char path[MAX_PATH];
     GetModuleFileName(NULL, path, MAX_PATH);
@@ -49,7 +51,7 @@
         throw std::runtime_error("Failed to connect to database.");
     }
 
-    // 创建设备表
+    // 鍒涘缓璁惧琛�
     const std::string createDevicesTableQuery = R"(
         CREATE TABLE IF NOT EXISTS devices (
             device_id TEXT PRIMARY KEY NOT NULL,
@@ -60,7 +62,7 @@
         return false;
     }
 
-    // 创建单元表,设备ID和单元ID组合作为主键
+    // 鍒涘缓鍗曞厓琛紝璁惧ID鍜屽崟鍏僆D缁勫悎浣滀负涓婚敭
     const std::string createUnitsTableQuery = R"(
         CREATE TABLE IF NOT EXISTS units (
             device_id TEXT NOT NULL,
@@ -74,7 +76,7 @@
         return false;
     }
 
-    // 创建报警表,报警记录的alarm_event_id是主键
+    // 鍒涘缓鎶ヨ琛紝鎶ヨ璁板綍鐨刟larm_event_id鏄富閿�
     const std::string createAlarmsTableQuery = R"(
         CREATE TABLE IF NOT EXISTS alarms (
             alarm_event_id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -93,8 +95,9 @@
         return false;
     }
 
-    // 设备列表 (ID -> 名称)
+    // 璁惧鍒楄〃 (ID -> 鍚嶇О)
     std::vector<std::pair<int, std::string>> devices = {
+        {0, "Software"},
         {EQ_ID_LOADPORT1, EQ_NAME_LOADPORT1},
         {EQ_ID_LOADPORT2, EQ_NAME_LOADPORT2},
         {EQ_ID_LOADPORT3, EQ_NAME_LOADPORT3},
@@ -113,12 +116,12 @@
         {EQ_ID_OPERATOR_REMOVE, EQ_NAME_OPERATOR_REMOVE}
     };
 
-    // 插入 devices 和对应的默认 unit
+    // 鎻掑叆 devices 鍜屽搴旂殑榛樿 unit
     for (const auto& dev : devices) {
         int nDeviceId = dev.first;
         const std::string& strDeviceName = dev.second;
 
-        // 插入设备
+        // 鎻掑叆璁惧
         std::ostringstream ossDev;
         ossDev << "INSERT OR IGNORE INTO devices (device_id, device_name) VALUES("
             << nDeviceId << ", '" << strDeviceName << "')";
@@ -126,7 +129,7 @@
             return false;
         }
 
-        // 插入默认单元 (unit_id = 0, unit_name = device_name)
+        // 鎻掑叆榛樿鍗曞厓 (unit_id = 0, unit_name = device_name)
         std::ostringstream ossUnit;
         ossUnit << "INSERT OR IGNORE INTO units (device_id, unit_id, unit_name) VALUES("
             << nDeviceId << ", 0, '" << strDeviceName << "')";
@@ -138,14 +141,14 @@
     return true;
 }
 
-// 销毁报警表
+// 閿�姣佹姤璀﹁〃
 void AlarmManager::termAlarmTable() {
     if (m_pDB != nullptr) {
 		m_pDB->disconnect();
     }
 }
 
-// 销毁报警表
+// 閿�姣佹姤璀﹁〃
 bool AlarmManager::destroyAlarmTable() {
 	if (!m_pDB) {
 		throw std::runtime_error("Database connection is not set.");
@@ -154,9 +157,9 @@
 	return m_pDB->executeQuery(dropTableQuery);
 }
 
-// 插入模拟数据
+// 鎻掑叆妯℃嫙鏁版嵁
 void AlarmManager::insertMockData() {
-    // 插入设备数据
+    // 鎻掑叆璁惧鏁版嵁
     for (int i = 1; i <= 3; ++i) {
         std::string deviceName = "Device" + std::to_string(i);
         std::stringstream query;
@@ -166,7 +169,7 @@
         }
     }
 
-    // 插入单元数据
+    // 鎻掑叆鍗曞厓鏁版嵁
     for (int i = 1; i <= 3; ++i) {
         for (int j = 0; j <= 3; ++j) {
             int unitId = j;
@@ -175,8 +178,8 @@
 
             std::stringstream query;
             query << "INSERT INTO units (device_id, unit_id, unit_name) VALUES ('"
-                << deviceId << "', '"   // 插入设备ID,确保是字符串
-                << unitId << "', '"     // 插入单元ID,确保是字符串
+                << deviceId << "', '"   // 鎻掑叆璁惧ID锛岀‘淇濇槸瀛楃涓�
+                << unitId << "', '"     // 鎻掑叆鍗曞厓ID锛岀‘淇濇槸瀛楃涓�
                 << unitName << "');";
 
             if (!m_pDB->executeQuery(query.str())) {
@@ -186,7 +189,7 @@
     }
 
     /*
-    // 初始化随机数生成器
+    // 鍒濆鍖栭殢鏈烘暟鐢熸垚鍣�
     std::random_device rd;
     std::mt19937 gen(rd());
     std::uniform_int_distribution<> deviceDis(1, 3);
@@ -195,7 +198,7 @@
     std::uniform_int_distribution<> severityDis(0, 3);
     std::vector<std::string> descriptions = { "Overheat", "Sensor failure", "Power outage" };
 
-    // 时间相关
+    // 鏃堕棿鐩稿叧
     auto now = std::chrono::system_clock::now();
     auto start_time = std::chrono::system_clock::to_time_t(now);
     auto end_time = std::chrono::system_clock::to_time_t(now + std::chrono::minutes(10));
@@ -205,13 +208,13 @@
     localtime_s(&start_tm, &start_time);
     localtime_s(&end_tm, &end_time);
 
-    // 插入模拟数据
+    // 鎻掑叆妯℃嫙鏁版嵁
     for (int i = 0; i < 10; ++i) {
-        int deviceId = deviceDis(gen);          // 随机设备ID
-        int unitId = unitDis(gen);              // 随机单元ID
-        int errorCode = errorCodeDis(gen);      // 随机错误码
-        int severityLevel = severityDis(gen);   // 随机生成报警等级
-        std::string description = descriptions[errorCodeDis(gen) % descriptions.size()];  // 随机报警描述
+        int deviceId = deviceDis(gen);          // 闅忔満璁惧ID
+        int unitId = unitDis(gen);              // 闅忔満鍗曞厓ID
+        int errorCode = errorCodeDis(gen);      // 闅忔満閿欒鐮�
+        int severityLevel = severityDis(gen);   // 闅忔満鐢熸垚鎶ヨ绛夌骇
+        std::string description = descriptions[errorCodeDis(gen) % descriptions.size()];  // 闅忔満鎶ヨ鎻忚堪
 
         std::stringstream query;
         query << "INSERT INTO alarms (id, severity_level, device_id, unit_id, description, start_time, end_time) "
@@ -226,38 +229,38 @@
     */
 }
 
-// 添加报警信息
+// 娣诲姞鎶ヨ淇℃伅
 bool AlarmManager::addAlarm(const AlarmData& alarmData, int& alarmEventId) {
     if (!m_pDB) {
         return false;
     }
 
     #if 0
-        // 开始事务
+        // 寮�濮嬩簨鍔�
         m_pDB->executeQuery("BEGIN TRANSACTION;");
     
-        // 构建插入查询
+        // 鏋勫缓鎻掑叆鏌ヨ
         std::ostringstream query;
         query << "INSERT INTO alarms (id, severity_level, device_id, unit_id, description, start_time, end_time) VALUES ("
-            << alarmData.nId << ", "              // 错误码
-            << alarmData.nSeverityLevel << ", "   // 报警等级
-            << alarmData.nDeviceId << ", "        // 设备ID
-            << alarmData.nUnitId << ", '"         // 单元ID
-            << alarmData.strDescription << "', '" // 描述
-            << alarmData.strStartTime << "', '"   // 开始时间
-            << alarmData.strEndTime << "')";      // 结束时间
+            << alarmData.nId << ", "              // 閿欒鐮�
+            << alarmData.nSeverityLevel << ", "   // 鎶ヨ绛夌骇
+            << alarmData.nDeviceId << ", "        // 璁惧ID
+            << alarmData.nUnitId << ", '"         // 鍗曞厓ID
+            << alarmData.strDescription << "', '" // 鎻忚堪
+            << alarmData.strStartTime << "', '"   // 寮�濮嬫椂闂�
+            << alarmData.strEndTime << "')";      // 缁撴潫鏃堕棿
     
-        // 使用锁保护多线程安全
+        // 浣跨敤閿佷繚鎶ゅ绾跨▼瀹夊叏
         std::lock_guard<std::mutex> lock(m_mutex);
     
-        // 执行插入查询
+        // 鎵ц鎻掑叆鏌ヨ
         bool result = m_pDB->executeQuery(query.str());
         if (result) {
             alarmEventId = getLastInsertId();
             m_alarmCache[alarmEventId] = alarmData;
         }
     
-        // 提交事务
+        // 鎻愪氦浜嬪姟
         m_pDB->executeQuery("COMMIT;");
     
         return result;
@@ -273,23 +276,23 @@
             }
         }
 
-        // 构建插入查询并使用 RETURNING 获取插入后的 alarm_event_id
+        // 鏋勫缓鎻掑叆鏌ヨ骞朵娇鐢� RETURNING 鑾峰彇鎻掑叆鍚庣殑 alarm_event_id
         std::ostringstream query;
         query << "INSERT INTO alarms (id, severity_level, device_id, unit_id, description, start_time, end_time) "
             << "VALUES (" << alarmData.nId << ", " << alarmData.nSeverityLevel << ", " << alarmData.nDeviceId << ", "
             << alarmData.nUnitId << ", '" << alarmData.strDescription << "', '" << alarmData.strStartTime << "', '"
             << alarmData.strEndTime << "') RETURNING alarm_event_id;";
     
-        // 使用锁保护多线程安全
+        // 浣跨敤閿佷繚鎶ゅ绾跨▼瀹夊叏
         std::lock_guard<std::mutex> lock(m_mutex);
     
-        // 执行查询并获取结果
+        // 鎵ц鏌ヨ骞惰幏鍙栫粨鏋�
         auto results = m_pDB->fetchResults(query.str());
         if (!results.empty() && !results[0].empty()) {
             try {
-                // 提取并转换 alarm_event_id
+                // 鎻愬彇骞惰浆鎹� alarm_event_id
                 alarmEventId = std::stoi(results[0][0]);
-                // 将插入的报警数据添加到缓存
+                // 灏嗘彃鍏ョ殑鎶ヨ鏁版嵁娣诲姞鍒扮紦瀛�
                 m_mapCache[alarmEventId] = alarmData;
                 return true;
             }
@@ -303,13 +306,13 @@
     #endif
 }
 
-// 查询所有报警数据
+// 鏌ヨ鎵�鏈夋姤璀︽暟鎹�
 std::vector<AlarmData> AlarmManager::getAllAlarms() {
     if (!m_pDB) {
         return {};
     }
 
-    // 查询所有报警数据(包括设备名称和单元名称)
+    // 鏌ヨ鎵�鏈夋姤璀︽暟鎹紙鍖呮嫭璁惧鍚嶇О鍜屽崟鍏冨悕绉帮級
     const std::string query = R"(
         SELECT a.id, a.severity_level, a.device_id, a.unit_id, d.device_name, u.unit_name, a.description, a.start_time, a.end_time
         FROM alarms a
@@ -319,19 +322,19 @@
 
     auto results = m_pDB->fetchResults(query);
 
-    // 遍历查询结果,填充 AlarmData 结构体
+    // 閬嶅巻鏌ヨ缁撴灉锛屽~鍏� AlarmData 缁撴瀯浣�
     std::vector<AlarmData> alarms;
     for (const auto& row : results) {
         AlarmData alarmData;
-        alarmData.nId = std::stoi(row[0]);              // 错误码
-        alarmData.nSeverityLevel = std::stoi(row[1]);   // 报警等级
-        alarmData.nDeviceId = std::stoi(row[2]);        // 设备ID
-        alarmData.nUnitId = std::stoi(row[3]);          // 单元ID
-        alarmData.strDeviceName = row[4];               // 设备名称
-        alarmData.strUnitName = row[5];                 // 单元名称
-        alarmData.strDescription = row[6];              // 描述
-        alarmData.strStartTime = row[7];                // 开始时间
-        alarmData.strEndTime = row[8];                  // 结束时间
+        alarmData.nId = std::stoi(row[0]);              // 閿欒鐮�
+        alarmData.nSeverityLevel = std::stoi(row[1]);   // 鎶ヨ绛夌骇
+        alarmData.nDeviceId = std::stoi(row[2]);        // 璁惧ID
+        alarmData.nUnitId = std::stoi(row[3]);          // 鍗曞厓ID
+        alarmData.strDeviceName = row[4];               // 璁惧鍚嶇О
+        alarmData.strUnitName = row[5];                 // 鍗曞厓鍚嶇О
+        alarmData.strDescription = row[6];              // 鎻忚堪
+        alarmData.strStartTime = row[7];                // 寮�濮嬫椂闂�
+        alarmData.strEndTime = row[8];                  // 缁撴潫鏃堕棿
 
         alarms.push_back(alarmData);
     }
@@ -339,7 +342,7 @@
     return alarms;
 }
 
-// 根据报警ID查询报警
+// 鏍规嵁鎶ヨID鏌ヨ鎶ヨ
 std::vector<AlarmData> AlarmManager::getAlarmsById(const std::string& id) {
     if (!m_pDB) {
         return {};
@@ -355,19 +358,19 @@
 
     auto results = m_pDB->fetchResults(query.str());
 
-    // 遍历查询结果,填充 AlarmData 结构体
+    // 閬嶅巻鏌ヨ缁撴灉锛屽~鍏� AlarmData 缁撴瀯浣�
     std::vector<AlarmData> alarms;
     for (const auto& row : results) {
         AlarmData alarmData;
-        alarmData.nId = std::stoi(row[0]);              // 错误码
-        alarmData.nSeverityLevel = std::stoi(row[1]);   // 报警等级
-        alarmData.nDeviceId = std::stoi(row[2]);        // 设备ID
-        alarmData.nUnitId = std::stoi(row[3]);          // 单元ID
-        alarmData.strDeviceName = row[4];               // 设备名称
-        alarmData.strUnitName = row[5];                 // 单元名称
-        alarmData.strDescription = row[6];              // 描述
-        alarmData.strStartTime = row[7];                // 开始时间
-        alarmData.strEndTime = row[8];                  // 结束时间
+        alarmData.nId = std::stoi(row[0]);              // 閿欒鐮�
+        alarmData.nSeverityLevel = std::stoi(row[1]);   // 鎶ヨ绛夌骇
+        alarmData.nDeviceId = std::stoi(row[2]);        // 璁惧ID
+        alarmData.nUnitId = std::stoi(row[3]);          // 鍗曞厓ID
+        alarmData.strDeviceName = row[4];               // 璁惧鍚嶇О
+        alarmData.strUnitName = row[5];                 // 鍗曞厓鍚嶇О
+        alarmData.strDescription = row[6];              // 鎻忚堪
+        alarmData.strStartTime = row[7];                // 寮�濮嬫椂闂�
+        alarmData.strEndTime = row[8];                  // 缁撴潫鏃堕棿
 
         alarms.push_back(alarmData);
     }
@@ -375,7 +378,7 @@
     return alarms;
 }
 
-// 根据描述查询报警
+// 鏍规嵁鎻忚堪鏌ヨ鎶ヨ
 std::vector<AlarmData> AlarmManager::getAlarmsByDescription(const std::string& description) {
     if (!m_pDB) {
         return {};
@@ -391,19 +394,19 @@
 
     auto results = m_pDB->fetchResults(query.str());
 
-    // 遍历查询结果,填充 AlarmData 结构体
+    // 閬嶅巻鏌ヨ缁撴灉锛屽~鍏� AlarmData 缁撴瀯浣�
     std::vector<AlarmData> alarms;
     for (const auto& row : results) {
         AlarmData alarmData;
-        alarmData.nId = std::stoi(row[0]);              // 错误码
-        alarmData.nSeverityLevel = std::stoi(row[1]);   // 报警等级
-        alarmData.nDeviceId = std::stoi(row[2]);        // 设备ID
-        alarmData.nUnitId = std::stoi(row[3]);          // 单元ID
-        alarmData.strDeviceName = row[4];               // 设备名称
-        alarmData.strUnitName = row[5];                 // 单元名称
-        alarmData.strDescription = row[6];              // 描述
-        alarmData.strStartTime = row[7];                // 开始时间
-        alarmData.strEndTime = row[8];                  // 结束时间
+        alarmData.nId = std::stoi(row[0]);              // 閿欒鐮�
+        alarmData.nSeverityLevel = std::stoi(row[1]);   // 鎶ヨ绛夌骇
+        alarmData.nDeviceId = std::stoi(row[2]);        // 璁惧ID
+        alarmData.nUnitId = std::stoi(row[3]);          // 鍗曞厓ID
+        alarmData.strDeviceName = row[4];               // 璁惧鍚嶇О
+        alarmData.strUnitName = row[5];                 // 鍗曞厓鍚嶇О
+        alarmData.strDescription = row[6];              // 鎻忚堪
+        alarmData.strStartTime = row[7];                // 寮�濮嬫椂闂�
+        alarmData.strEndTime = row[8];                  // 缁撴潫鏃堕棿
 
         alarms.push_back(alarmData);
     }
@@ -411,7 +414,7 @@
     return alarms;
 }
 
-// 根据时间范围查询报警
+// 鏍规嵁鏃堕棿鑼冨洿鏌ヨ鎶ヨ
 std::vector<AlarmData> AlarmManager::getAlarmsByTimeRange(const std::string& startTime, const std::string& endTime) {
     if (!m_pDB) {
         return {};
@@ -434,19 +437,19 @@
 
     auto results = m_pDB->fetchResults(query.str());
 
-    // 遍历查询结果,填充 AlarmData 结构体
+    // 閬嶅巻鏌ヨ缁撴灉锛屽~鍏� AlarmData 缁撴瀯浣�
     std::vector<AlarmData> alarms;
     for (const auto& row : results) {
         AlarmData alarmData;
-        alarmData.nId = std::stoi(row[0]);              // 错误码
-        alarmData.nSeverityLevel = std::stoi(row[1]);   // 报警等级
-        alarmData.nDeviceId = std::stoi(row[2]);        // 设备ID
-        alarmData.nUnitId = std::stoi(row[3]);          // 单元ID
-        alarmData.strDeviceName = row[4];               // 设备名称
-        alarmData.strUnitName = row[5];                 // 单元名称
-        alarmData.strDescription = row[6];              // 描述
-        alarmData.strStartTime = row[7];                // 开始时间
-        alarmData.strEndTime = row[8];                  // 结束时间
+        alarmData.nId = std::stoi(row[0]);              // 閿欒鐮�
+        alarmData.nSeverityLevel = std::stoi(row[1]);   // 鎶ヨ绛夌骇
+        alarmData.nDeviceId = std::stoi(row[2]);        // 璁惧ID
+        alarmData.nUnitId = std::stoi(row[3]);          // 鍗曞厓ID
+        alarmData.strDeviceName = row[4];               // 璁惧鍚嶇О
+        alarmData.strUnitName = row[5];                 // 鍗曞厓鍚嶇О
+        alarmData.strDescription = row[6];              // 鎻忚堪
+        alarmData.strStartTime = row[7];                // 寮�濮嬫椂闂�
+        alarmData.strEndTime = row[8];                  // 缁撴潫鏃堕棿
 
         alarms.push_back(alarmData);
     }
@@ -454,7 +457,7 @@
     return alarms;
 }
 
-// 根据ID、开始时间和结束时间查询报警
+// 鏍规嵁ID銆佸紑濮嬫椂闂村拰缁撴潫鏃堕棿鏌ヨ鎶ヨ
 std::vector<AlarmData> AlarmManager::getAlarmsByIdAndTimeRange(const std::string& id, const std::string& startTime, const std::string& endTime) {
     if (!m_pDB) {
         return {};
@@ -477,19 +480,19 @@
 
     auto results = m_pDB->fetchResults(query.str());
 
-    // 遍历查询结果,填充 AlarmData 结构体
+    // 閬嶅巻鏌ヨ缁撴灉锛屽~鍏� AlarmData 缁撴瀯浣�
     std::vector<AlarmData> alarms;
     for (const auto& row : results) {
         AlarmData alarmData;
-        alarmData.nId = std::stoi(row[0]);              // 错误码
-        alarmData.nSeverityLevel = std::stoi(row[1]);   // 报警等级
-        alarmData.nDeviceId = std::stoi(row[2]);        // 设备ID
-        alarmData.nUnitId = std::stoi(row[3]);          // 单元ID
-        alarmData.strDeviceName = row[4];               // 设备名称
-        alarmData.strUnitName = row[5];                 // 单元名称
-        alarmData.strDescription = row[6];              // 描述
-        alarmData.strStartTime = row[7];                // 开始时间
-        alarmData.strEndTime = row[8];                  // 结束时间
+        alarmData.nId = std::stoi(row[0]);              // 閿欒鐮�
+        alarmData.nSeverityLevel = std::stoi(row[1]);   // 鎶ヨ绛夌骇
+        alarmData.nDeviceId = std::stoi(row[2]);        // 璁惧ID
+        alarmData.nUnitId = std::stoi(row[3]);          // 鍗曞厓ID
+        alarmData.strDeviceName = row[4];               // 璁惧鍚嶇О
+        alarmData.strUnitName = row[5];                 // 鍗曞厓鍚嶇О
+        alarmData.strDescription = row[6];              // 鎻忚堪
+        alarmData.strStartTime = row[7];                // 寮�濮嬫椂闂�
+        alarmData.strEndTime = row[8];                  // 缁撴潫鏃堕棿
 
         alarms.push_back(alarmData);
     }
@@ -497,7 +500,7 @@
     return alarms;
 }
 
-// 分页查询报警数据
+// 鍒嗛〉鏌ヨ鎶ヨ鏁版嵁
 std::vector<AlarmData> AlarmManager::getAlarms(int startPosition, int count) {
     if (!m_pDB) {
         return {};
@@ -513,19 +516,19 @@
 
     auto results = m_pDB->fetchResults(query.str());
 
-    // 遍历查询结果,填充 AlarmData 结构体
+    // 閬嶅巻鏌ヨ缁撴灉锛屽~鍏� AlarmData 缁撴瀯浣�
     std::vector<AlarmData> alarms;
     for (const auto& row : results) {
         AlarmData alarmData;
-        alarmData.nId = std::stoi(row[0]);              // 错误码
-        alarmData.nSeverityLevel = std::stoi(row[1]);   // 报警等级
-        alarmData.nDeviceId = std::stoi(row[2]);        // 设备ID
-        alarmData.nUnitId = std::stoi(row[3]);          // 单元ID
-        alarmData.strDeviceName = row[4];               // 设备名称
-        alarmData.strUnitName = row[5];                 // 单元名称
-        alarmData.strDescription = row[6];              // 描述
-        alarmData.strStartTime = row[7];                // 开始时间
-        alarmData.strEndTime = row[8];                  // 结束时间
+        alarmData.nId = std::stoi(row[0]);              // 閿欒鐮�
+        alarmData.nSeverityLevel = std::stoi(row[1]);   // 鎶ヨ绛夌骇
+        alarmData.nDeviceId = std::stoi(row[2]);        // 璁惧ID
+        alarmData.nUnitId = std::stoi(row[3]);          // 鍗曞厓ID
+        alarmData.strDeviceName = row[4];               // 璁惧鍚嶇О
+        alarmData.strUnitName = row[5];                 // 鍗曞厓鍚嶇О
+        alarmData.strDescription = row[6];              // 鎻忚堪
+        alarmData.strStartTime = row[7];                // 寮�濮嬫椂闂�
+        alarmData.strEndTime = row[8];                  // 缁撴潫鏃堕棿
 
         alarms.push_back(alarmData);
     }
@@ -533,7 +536,86 @@
     return alarms;
 }
 
-// 筛选报警数据
+// 鑾峰彇褰撳墠鏈粨鏉熺殑鎶ヨ锛坋nd_time 涓虹┖锛夛紝骞跺彲鎸� start_time 鏈�杩� N 灏忔椂杩囨护
+std::vector<AlarmData> AlarmManager::getActiveAlarms(int recentHours /*=12*/) {
+    if (!m_pDB) {
+        return {};
+    }
+
+    // 璁$畻鏃堕棿闃堝�硷細褰撳墠鏃堕棿鍑� recentHours
+    std::string cutoffTime;
+    if (recentHours > 0) {
+        using namespace std::chrono;
+        auto cutoff = system_clock::now() - hours(recentHours);
+        std::time_t t = system_clock::to_time_t(cutoff);
+        std::tm tm {};
+        localtime_s(&tm, &t);
+        char buf[32] = { 0 };
+        std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm);
+        cutoffTime = buf;
+    }
+
+    std::ostringstream query;
+    query << R"(
+        SELECT a.id, a.severity_level, a.device_id, a.unit_id, d.device_name, u.unit_name, a.description, a.start_time, a.end_time
+        FROM alarms a
+        JOIN devices d ON a.device_id = d.device_id
+        JOIN units u ON a.device_id = u.device_id AND a.unit_id = u.unit_id
+        WHERE a.end_time IS NULL OR a.end_time = ''
+    )";
+    if (!cutoffTime.empty()) {
+        query << " AND a.start_time >= '" << cutoffTime << "'";
+    }
+    query << " ORDER BY a.start_time DESC";
+
+    auto lastErrStr = []() -> std::string {
+        DWORD gle = GetLastError();
+        if (gle == 0) return {};
+        LPSTR buf = nullptr;
+        size_t len = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+            nullptr, gle, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buf, 0, nullptr);
+        std::string s = (buf && len > 0) ? std::string(buf, len) : "";
+        if (buf) LocalFree(buf);
+        return s;
+    };
+    std::vector<std::vector<std::string>> results;
+    try {
+        results = m_pDB->fetchResults(query.str());
+    }
+    catch (const std::exception& ex) {
+        DWORD gle = GetLastError();
+        auto errStr = lastErrStr();
+        LOGE("<AlarmManager>getActiveAlarms failed: %s, GLE=%lu (%s)", ex.what(), gle, errStr.c_str());
+        return {};
+    }
+    std::vector<AlarmData> alarms;
+    auto toInt = [](const std::string& s) -> int {
+        try {
+            return std::stoi(s);
+        }
+        catch (...) {
+            return 0;
+        }
+    };
+    for (const auto& row : results) {
+        if (row.size() < 9) continue;
+        AlarmData alarmData;
+        alarmData.nId = toInt(row[0]);
+        alarmData.nSeverityLevel = toInt(row[1]);
+        alarmData.nDeviceId = toInt(row[2]);
+        alarmData.nUnitId = toInt(row[3]);
+        alarmData.strDeviceName = row[4];
+        alarmData.strUnitName = row[5];
+        alarmData.strDescription = row[6];
+        alarmData.strStartTime = row[7];
+        alarmData.strEndTime = row[8];
+        alarms.push_back(alarmData);
+    }
+
+    return alarms;
+}
+
+// 绛涢�夋姤璀︽暟鎹�
 std::vector<AlarmData> AlarmManager::getFilteredAlarms(const std::string& keyword, const std::string& startTime, const std::string& endTime, int pageNumber, int pageSize) {
     if (!m_pDB) {
         return {};
@@ -547,7 +629,7 @@
         JOIN units u   ON a.device_id = u.device_id AND a.unit_id = u.unit_id
         WHERE 1=1)";
 
-    // 统一关键字模糊查询
+    // 缁熶竴鍏抽敭瀛楁ā绯婃煡璇�
     if (!keyword.empty()) {
         query << " AND ("
             << "a.id LIKE '%" << keyword << "%' OR "
@@ -557,7 +639,7 @@
             << "a.description LIKE '%" << keyword << "%')";
     }
 
-    // 时间条件
+    // 鏃堕棿鏉′欢
     if (!startTime.empty()) {
         query << " AND a.start_time >= '" << startTime << "'";
     }
@@ -565,26 +647,26 @@
         query << " AND a.end_time <= '" << endTime << "'";
     }
 
-    // 分页设置
+    // 鍒嗛〉璁剧疆
     int nOffset = (pageNumber - 1) * pageSize;
     query << " ORDER BY a.start_time DESC LIMIT " << pageSize << " OFFSET " << nOffset;
 
-    // 查询
+    // 鏌ヨ
     auto results = m_pDB->fetchResults(query.str());
 
-    // 遍历查询结果,填充 AlarmData 结构体
+    // 閬嶅巻鏌ヨ缁撴灉锛屽~鍏� AlarmData 缁撴瀯浣�
     std::vector<AlarmData> alarms;
     for (const auto& row : results) {
         AlarmData alarmData;
-        alarmData.nId = std::stoi(row[0]);             // 错误码
-        alarmData.nSeverityLevel = std::stoi(row[1]);  // 报警等级 (字符串)
-        alarmData.nDeviceId = std::stoi(row[2]);       // 设备ID
-        alarmData.nUnitId = std::stoi(row[3]);         // 单元ID
-        alarmData.strDeviceName = row[4];              // 设备名称
-        alarmData.strUnitName = row[5];                // 单元名称
-        alarmData.strDescription = row[6];             // 描述
-        alarmData.strStartTime = row[7];               // 开始时间
-        alarmData.strEndTime = row[8];                 // 结束时间
+        alarmData.nId = std::stoi(row[0]);             // 閿欒鐮�
+        alarmData.nSeverityLevel = std::stoi(row[1]);  // 鎶ヨ绛夌骇 (瀛楃涓�)
+        alarmData.nDeviceId = std::stoi(row[2]);       // 璁惧ID
+        alarmData.nUnitId = std::stoi(row[3]);         // 鍗曞厓ID
+        alarmData.strDeviceName = row[4];              // 璁惧鍚嶇О
+        alarmData.strUnitName = row[5];                // 鍗曞厓鍚嶇О
+        alarmData.strDescription = row[6];             // 鎻忚堪
+        alarmData.strStartTime = row[7];               // 寮�濮嬫椂闂�
+        alarmData.strEndTime = row[8];                 // 缁撴潫鏃堕棿
 
         alarms.push_back(alarmData);
     }
@@ -592,7 +674,7 @@
     return alarms;
 }
 
-// 获取符合条件的报警总数
+// 鑾峰彇绗﹀悎鏉′欢鐨勬姤璀︽�绘暟
 int AlarmManager::getTotalAlarmCount(const std::string& keyword, const std::string& startTime, const std::string& endTime) {
     if (!m_pDB) {
         return 0;
@@ -606,7 +688,7 @@
         JOIN units u   ON a.device_id = u.device_id AND a.unit_id = u.unit_id
         WHERE 1=1)";
 
-    // 统一关键字模糊查询
+    // 缁熶竴鍏抽敭瀛楁ā绯婃煡璇�
     if (!keyword.empty()) {
         query << " AND ("
             << "a.id LIKE '%" << keyword << "%' OR "
@@ -616,7 +698,7 @@
             << "a.description LIKE '%" << keyword << "%')";
     }
 
-    // 时间条件
+    // 鏃堕棿鏉′欢
     if (!startTime.empty()) {
         query << " AND a.start_time >= '" << startTime << "'";
     }
@@ -628,7 +710,7 @@
     return (!results.empty() && !results[0].empty()) ? std::stoi(results[0][0]) : 0;
 }
 
-// 更新报警的结束时间
+// 鏇存柊鎶ヨ鐨勭粨鏉熸椂闂�
 bool AlarmManager::updateAlarmEndTime(
     const std::string& id,
     const std::string& severityLevel,
@@ -642,7 +724,7 @@
         return false;
     }
 
-    // 更新报警结束时间
+    // 鏇存柊鎶ヨ缁撴潫鏃堕棿
     std::ostringstream updateQuery;
     updateQuery << "UPDATE alarms SET end_time = '" << newEndTime << "'"
         << " WHERE id = '" << id << "'"
@@ -655,7 +737,7 @@
     return m_pDB->executeQuery(updateQuery.str());
 }
 
-// 清理旧报警数据
+// 娓呯悊鏃ф姤璀︽暟鎹�
 void AlarmManager::cleanOldAlarms(int daysToKeep, const std::string& deviceId, const std::string& unitId) {
     if (!m_pDB) {
         return;
@@ -674,7 +756,7 @@
     m_pDB->executeQuery(query.str());
 }
 
-// 通过设备ID获取设备名称
+// 閫氳繃璁惧ID鑾峰彇璁惧鍚嶇О
 std::string AlarmManager::getDeviceNameById(int deviceId) {
     if (!m_pDB) {
         return "";
@@ -688,10 +770,10 @@
         return "";
     }
 
-    return result[0][0];  // 返回查询到的设备名称
+    return result[0][0];  // 杩斿洖鏌ヨ鍒扮殑璁惧鍚嶇О
 }
 
-// 通过设备ID和单元ID获取单元名称
+// 閫氳繃璁惧ID鍜屽崟鍏僆D鑾峰彇鍗曞厓鍚嶇О
 std::string AlarmManager::getUnitNameById(int deviceId, int unitId) {
     if (!m_pDB) {
         return "";
@@ -706,10 +788,10 @@
         return "";
     }
 
-    return result[0][0];  // 返回查询到的单元名称
+    return result[0][0];  // 杩斿洖鏌ヨ鍒扮殑鍗曞厓鍚嶇О
 }
 
-// 获取最近插入的 alarm_event_id
+// 鑾峰彇鏈�杩戞彃鍏ョ殑 alarm_event_id
 int AlarmManager::getLastInsertId() {
     std::string query = "SELECT last_insert_rowid();";
     auto results = m_pDB->fetchResults(query);
@@ -718,12 +800,12 @@
         return -1;
     }
 
-    // 从查询结果中获取最后插入的 ID
+    // 浠庢煡璇㈢粨鏋滀腑鑾峰彇鏈�鍚庢彃鍏ョ殑 ID
     int lastInsertId = std::stoi(results[0][0]);
     return lastInsertId;
 }
 
-// 通过 alarm_event_id 解除报警(更新结束时间)
+// 閫氳繃 alarm_event_id 瑙i櫎鎶ヨ锛堟洿鏂扮粨鏉熸椂闂达級
 bool AlarmManager::clearAlarmByEventId(int alarmEventId, const std::string& endTime) {
     if (!m_pDB) {
         return false;
@@ -745,7 +827,7 @@
     return result;
 }
 
-// 通过多个属性查找并解除报警(更新结束时间)
+// 閫氳繃澶氫釜灞炴�ф煡鎵惧苟瑙i櫎鎶ヨ锛堟洿鏂扮粨鏉熸椂闂达級
 bool AlarmManager::clearAlarmByAttributes(int nId, int nDeviceId, int nUnitId, const std::string& endTime) {
     if (!m_pDB) {
         return false;
@@ -753,7 +835,7 @@
 
     std::lock_guard<std::mutex> lock(m_mutex);
 
-    // 先在缓存中查找匹配的 alarm_event_id
+    // 鍏堝湪缂撳瓨涓煡鎵惧尮閰嶇殑 alarm_event_id
     int alarmEventId = -1;
     for (AlarmDataMap::const_iterator it = m_mapCache.begin(); it != m_mapCache.end(); ++it) {
         const AlarmData& alarm = it->second;
@@ -766,12 +848,32 @@
         }
     }
 
-    // 如果没找到匹配的记录,则直接返回 false
-    if (alarmEventId == -1) {
-        return false;
-    }
+	// 缂撳瓨鏈懡涓椂锛屼粠鏁版嵁搴撴煡鎵句粛鏈粨鏉熺殑璁板綍锛堝彇鏈�鏂颁竴鏉★級
+	if (alarmEventId == -1) {
+		std::ostringstream querySel;
+		querySel << "SELECT alarm_event_id FROM alarms WHERE "
+			<< "id = " << nId
+			<< " AND device_id = " << nDeviceId
+			<< " AND unit_id = " << nUnitId
+			<< " AND (end_time IS NULL OR end_time = '') "
+			<< "ORDER BY start_time DESC LIMIT 1;";
+		auto results = m_pDB->fetchResults(querySel.str());
+		if (!results.empty() && !results[0].empty()) {
+			try {
+				alarmEventId = std::stoi(results[0][0]);
+			}
+			catch (...) {
+				alarmEventId = -1;
+			}
+		}
+	}
 
-    // 构建 SQL 语句,使用找到的 alarm_event_id 来更新结束时间
+	// 濡傛灉娌℃壘鍒板尮閰嶇殑璁板綍锛屽垯鐩存帴杩斿洖 false
+	if (alarmEventId == -1) {
+		return false;
+	}
+
+    // 鏋勫缓 SQL 璇彞锛屼娇鐢ㄦ壘鍒扮殑 alarm_event_id 鏉ユ洿鏂扮粨鏉熸椂闂�
     std::ostringstream query;
     query << "UPDATE alarms SET end_time = '" << endTime << "' WHERE alarm_event_id = " << alarmEventId << ";";
     bool result = m_pDB->executeQuery(query.str());
@@ -782,7 +884,7 @@
     return result;
 }
 
-// 读取报警文件
+// 璇诲彇鎶ヨ鏂囦欢
 bool AlarmManager::readAlarmFile(const std::string& filename) {
     std::ifstream file(filename, std::ios::binary);
     if (!file.is_open()) {
@@ -795,7 +897,7 @@
         char ch;
         while (f.get(ch)) {
             if (ch == '\r') {
-                // 处理 \r\n 或 单独 \r
+                // 澶勭悊 \r\n 鎴� 鍗曠嫭 \r
                 if (f.peek() == '\n') f.get();
                 break;
             }
@@ -850,19 +952,19 @@
     return true;
 }
 
-// 将报警数据保存到文件
+// 灏嗘姤璀︽暟鎹繚瀛樺埌鏂囦欢
 bool AlarmManager::saveAlarmFile(const std::string& filename) {
     std::ofstream file(filename);
 
     if (!file.is_open()) {
-        std::cerr << "打开文件写入失败!" << std::endl;
+        std::cerr << "鎵撳紑鏂囦欢鍐欏叆澶辫触!" << std::endl;
         return false;
     }
 
-    // 写入标题行
+    // 鍐欏叆鏍囬琛�
     file << "No,UNIT ID,UNIT NO,Alarm Level,Alarm Code,AlarmID,Alarm Text,Description\n";
 
-    // 写入报警数据
+    // 鍐欏叆鎶ヨ鏁版嵁
     int nIndex = 1;
     for (const auto& pair : m_mapAlarm) {
         const AlarmInfo& alarm = pair.second;
@@ -880,19 +982,19 @@
     return true;
 }
 
-// 通过 AlarmID 查询对应的报警信息
+// 閫氳繃 AlarmID 鏌ヨ瀵瑰簲鐨勬姤璀︿俊鎭�
 const AlarmInfo* AlarmManager::getAlarmInfoByID(int nAlarmID) const {
     auto it = m_mapAlarm.find(nAlarmID);
     if (it != m_mapAlarm.end()) {
         return &(it->second);
     }
     else {
-        std::cerr << "未找到 AlarmID: " << nAlarmID << std::endl;
+        std::cerr << "鏈壘鍒� AlarmID: " << nAlarmID << std::endl;
         return nullptr;
     }
 }
 
-// 通过多个 AlarmID 查询对应的报警信息
+// 閫氳繃澶氫釜 AlarmID 鏌ヨ瀵瑰簲鐨勬姤璀︿俊鎭�
 std::vector<AlarmInfo> AlarmManager::getAlarmsInfoByIDs(const std::vector<int>& alarmIDs) const {
     std::vector<AlarmInfo> alarms;
 
@@ -902,7 +1004,7 @@
             alarms.push_back(it->second);
         } 
         else {
-            std::cerr << "未找到 AlarmID: " << alarmID << std::endl;
+            std::cerr << "鏈壘鍒� AlarmID: " << alarmID << std::endl;
         }
     }
 
diff --git a/SourceCode/Bond/Servo/AlarmManager.h b/SourceCode/Bond/Servo/AlarmManager.h
index d98fce9..1cd55a9 100644
--- a/SourceCode/Bond/Servo/AlarmManager.h
+++ b/SourceCode/Bond/Servo/AlarmManager.h
@@ -1,4 +1,4 @@
-#ifndef ALARM_MANAGER_H
+锘�#ifndef ALARM_MANAGER_H
 #define ALARM_MANAGER_H
 
 #include <string>
@@ -18,15 +18,15 @@
 };
 
 struct AlarmData {
-    int nId;                       // 错误码
-    int nSeverityLevel;            // 报警等级
-    int nDeviceId;                 // 设备ID
-    int nUnitId;                   // 单元ID
-    std::string strDeviceName;     // 设备名称
-    std::string strUnitName;       // 单元名称
-    std::string strDescription;    // 描述
-    std::string strStartTime;      // 开始时间
-    std::string strEndTime;        // 结束时间
+    int nId;                       // 閿欒鐮�
+    int nSeverityLevel;            // 鎶ヨ绛夌骇
+    int nDeviceId;                 // 璁惧ID
+    int nUnitId;                   // 鍗曞厓ID
+    std::string strDeviceName;     // 璁惧鍚嶇О
+    std::string strUnitName;       // 鍗曞厓鍚嶇О
+    std::string strDescription;    // 鎻忚堪
+    std::string strStartTime;      // 寮�濮嬫椂闂�
+    std::string strEndTime;        // 缁撴潫鏃堕棿
 };
 
 using AlarmInfoMap = std::unordered_map<int, AlarmInfo>;
@@ -35,116 +35,117 @@
 class AlarmManager {
 public:
     /**
-     * 获取单例实例
-     * @return AlarmManager实例的引用
+     * 鑾峰彇鍗曚緥瀹炰緥
+     * @return AlarmManager瀹炰緥鐨勫紩鐢�
      */
     static AlarmManager& getInstance();
 
     /**
-     * 初始化报警表
-     * @return 成功返回true,失败返回false
+     * 鍒濆鍖栨姤璀﹁〃
+     * @return 鎴愬姛杩斿洖true锛屽け璐ヨ繑鍥瀎alse
      */
     bool initAlarmTable();
 
 	/**
-	 * 销毁报警表
+	 * 閿�姣佹姤璀﹁〃
 	 */
     void termAlarmTable();
 
 	/**
-	 * 销毁报警表
-	 * @return 成功返回true,失败返回false
+	 * 閿�姣佹姤璀﹁〃
+	 * @return 鎴愬姛杩斿洖true锛屽け璐ヨ繑鍥瀎alse
 	 */
     bool destroyAlarmTable();
 
     /**
-    * 插入模拟数据
+    * 鎻掑叆妯℃嫙鏁版嵁
     */
     void insertMockData();
 
     /**
-     * 添加报警
-     * @param alarmData 报警数据的结构体
-     * @param alarmEventId 最近插入的 alarm_event_id
-     * @return 成功返回true,失败返回false
+     * 娣诲姞鎶ヨ
+     * @param alarmData 鎶ヨ鏁版嵁鐨勭粨鏋勪綋
+     * @param alarmEventId 鏈�杩戞彃鍏ョ殑 alarm_event_id
+     * @return 鎴愬姛杩斿洖true锛屽け璐ヨ繑鍥瀎alse
      */
     bool addAlarm(const AlarmData& alarmData, int& alarmEventId);
 
     /**
-     * 查询所有报警数据
-     * @return 包含所有报警数据的结构体
+     * 鏌ヨ鎵�鏈夋姤璀︽暟鎹�
+     * @return 鍖呭惈鎵�鏈夋姤璀︽暟鎹殑缁撴瀯浣�
      */
     std::vector<AlarmData> getAllAlarms();
 
     /**
-     * 根据报警ID查询报警
-     * @param id 报警ID
-     * @return 包含筛选后报警数据的结构体
+     * 鏍规嵁鎶ヨID鏌ヨ鎶ヨ
+     * @param id 鎶ヨID
+     * @return 鍖呭惈绛涢�夊悗鎶ヨ鏁版嵁鐨勭粨鏋勪綋
      */
     std::vector<AlarmData> getAlarmsById(const std::string& id);
 
     /**
-     * 根据描述查询报警
-     * @param description 报警描述的筛选条件
-     * @return 包含筛选后报警数据的结构体
+     * 鏍规嵁鎻忚堪鏌ヨ鎶ヨ
+     * @param description 鎶ヨ鎻忚堪鐨勭瓫閫夋潯浠�
+     * @return 鍖呭惈绛涢�夊悗鎶ヨ鏁版嵁鐨勭粨鏋勪綋
      */
     std::vector<AlarmData> getAlarmsByDescription(const std::string& description);
 
     /**
-     * 根据时间范围查询报警
-     * @param startTime 起始时间
-     * @param endTime 结束时间
-     * @return 包含查询结果的报警数据
+     * 鏍规嵁鏃堕棿鑼冨洿鏌ヨ鎶ヨ
+     * @param startTime 璧峰鏃堕棿
+     * @param endTime 缁撴潫鏃堕棿
+     * @return 鍖呭惈鏌ヨ缁撴灉鐨勬姤璀︽暟鎹�
      */
     std::vector<AlarmData> getAlarmsByTimeRange(const std::string& startTime, const std::string& endTime);
 
 	/**
-	* 根据ID和时间范围查询报警
-	 * @param id 报警ID
-	 * @param startTime 起始时间
-	 * @param endTime 结束时间
-	 * @return 包含查询结果的报警数据
+	* 鏍规嵁ID鍜屾椂闂磋寖鍥存煡璇㈡姤璀�
+	 * @param id 鎶ヨID
+	 * @param startTime 璧峰鏃堕棿
+	 * @param endTime 缁撴潫鏃堕棿
+	 * @return 鍖呭惈鏌ヨ缁撴灉鐨勬姤璀︽暟鎹�
 	 */
     std::vector<AlarmData> getAlarmsByIdAndTimeRange(const std::string& id, const std::string& startTime, const std::string& endTime);
 
     /**
-     * 获取报警数据
-     * @param startPosition 起始位置
-     * @param count 获取的记录数量
-     * @return 包含查询结果的报警数据
+     * 鑾峰彇鎶ヨ鏁版嵁
+     * @param startPosition 璧峰浣嶇疆
+     * @param count 鑾峰彇鐨勮褰曟暟閲�
+     * @return 鍖呭惈鏌ヨ缁撴灉鐨勬姤璀︽暟鎹�
      */
     std::vector<AlarmData> getAlarms(int startPosition, int count);
+    std::vector<AlarmData> getActiveAlarms(int recentHours = 12);
 
 	/**
-	 * 筛选报警数据
-	 * @param keyword 关键字筛选条件
-	 * @param startTime 起始时间筛选条件
-	 * @param endTime 结束时间筛选条件
-	 * @param pageNumber 页码
-	 * @param pageSize 每页记录数
-	 * @return 包含筛选后报警数据的结构体
+	 * 绛涢�夋姤璀︽暟鎹�
+	 * @param keyword 鍏抽敭瀛楃瓫閫夋潯浠�
+	 * @param startTime 璧峰鏃堕棿绛涢�夋潯浠�
+	 * @param endTime 缁撴潫鏃堕棿绛涢�夋潯浠�
+	 * @param pageNumber 椤电爜
+	 * @param pageSize 姣忛〉璁板綍鏁�
+	 * @return 鍖呭惈绛涢�夊悗鎶ヨ鏁版嵁鐨勭粨鏋勪綋
 	 */
     std::vector<AlarmData> getFilteredAlarms(const std::string& keyword, const std::string& startTime, const std::string& endTime, int pageNumber, int pageSize);
 
     /**
-     * 获取符合条件的报警总数
-     * @param keyword 关键字筛选条件
-     * @param startTime 起始时间筛选条件
-     * @param endTime 结束时间筛选条件
-     * @return 符合条件的报警总数
+     * 鑾峰彇绗﹀悎鏉′欢鐨勬姤璀︽�绘暟
+     * @param keyword 鍏抽敭瀛楃瓫閫夋潯浠�
+     * @param startTime 璧峰鏃堕棿绛涢�夋潯浠�
+     * @param endTime 缁撴潫鏃堕棿绛涢�夋潯浠�
+     * @return 绗﹀悎鏉′欢鐨勬姤璀︽�绘暟
      */
     int getTotalAlarmCount(const std::string& keyword, const std::string& startTime, const std::string& endTime);
 
 	/**
-	 * 更新报警结束时间
-	 * @param id 报警ID
-     * @param severityLevel 报警等级筛选条件
-     * @param deviceId 设备ID
-     * @param unitId 单元ID
-	 * @param description 报警描述
-	 * @param startTime 报警开始时间
-	 * @param newEndTime 新的报警结束时间
-	 * @return 成功返回true,失败返回false
+	 * 鏇存柊鎶ヨ缁撴潫鏃堕棿
+	 * @param id 鎶ヨID
+     * @param severityLevel 鎶ヨ绛夌骇绛涢�夋潯浠�
+     * @param deviceId 璁惧ID
+     * @param unitId 鍗曞厓ID
+	 * @param description 鎶ヨ鎻忚堪
+	 * @param startTime 鎶ヨ寮�濮嬫椂闂�
+	 * @param newEndTime 鏂扮殑鎶ヨ缁撴潫鏃堕棿
+	 * @return 鎴愬姛杩斿洖true锛屽け璐ヨ繑鍥瀎alse
 	 */
     bool updateAlarmEndTime(
         const std::string& id,
@@ -156,79 +157,79 @@
         const std::string& newEndTime);
 
     /**
-     * 清理旧报警
-     * @param daysToKeep 保留的天数
-	 * @param deviceId 设备ID
-     * @param unitId 单元ID
+     * 娓呯悊鏃ф姤璀�
+     * @param daysToKeep 淇濈暀鐨勫ぉ鏁�
+	 * @param deviceId 璁惧ID
+     * @param unitId 鍗曞厓ID
      */
     void cleanOldAlarms(int daysToKeep = 30, const std::string& deviceId = "", const std::string& unitId = "");
 
     /**
-    * 通过设备ID获取设备名称
-    * @param deviceId 设备ID
-    * @return 成功返回设备名称,失败返回空
+    * 閫氳繃璁惧ID鑾峰彇璁惧鍚嶇О
+    * @param deviceId 璁惧ID
+    * @return 鎴愬姛杩斿洖璁惧鍚嶇О锛屽け璐ヨ繑鍥炵┖
     */
     std::string getDeviceNameById(int deviceId);
 
     /**
-    * 通过设备ID和单元ID获取单元名称
-    * @param deviceId 设备ID
-    * @param unitId 单元ID
-    * @return 成功返回单元名称,失败返回空
+    * 閫氳繃璁惧ID鍜屽崟鍏僆D鑾峰彇鍗曞厓鍚嶇О
+    * @param deviceId 璁惧ID
+    * @param unitId 鍗曞厓ID
+    * @return 鎴愬姛杩斿洖鍗曞厓鍚嶇О锛屽け璐ヨ繑鍥炵┖
     */
     std::string getUnitNameById(int deviceId, int unitId);
 
     /**
-    * 获取最近插入的 alarm_event_id
-    * @return 失败返回-1,成功返回最近插入的 alarm_event_id
+    * 鑾峰彇鏈�杩戞彃鍏ョ殑 alarm_event_id
+    * @return 澶辫触杩斿洖-1锛屾垚鍔熻繑鍥炴渶杩戞彃鍏ョ殑 alarm_event_id
     */
     int getLastInsertId();
 
     /**
-    * 通过事件id解除报警(更新结束时间)
-    * @param alarmEventId 事件ID
-    * @param endTime 结束时间
-    * @return 成功返回true,失败返回false
+    * 閫氳繃浜嬩欢id瑙i櫎鎶ヨ锛堟洿鏂扮粨鏉熸椂闂达級
+    * @param alarmEventId 浜嬩欢ID
+    * @param endTime 缁撴潫鏃堕棿
+    * @return 鎴愬姛杩斿洖true锛屽け璐ヨ繑鍥瀎alse
     */
     bool clearAlarmByEventId(int alarmEventId, const std::string& endTime);
 
     /**
-    * 通过多个属性查找并解除报警(更新结束时间)
-    * @param nId 报警ID
-    * @param nSeverityLevel 报警等级
-    * @param nDeviceId 设备ID
-    * @param nUnitId 单元ID
-    * @param strDescription 描述
-    * @param endTime 结束时间
-    * @return 成功返回true,失败返回false
+    * 閫氳繃澶氫釜灞炴�ф煡鎵惧苟瑙i櫎鎶ヨ锛堟洿鏂扮粨鏉熸椂闂达級
+    * @param nId 鎶ヨID
+    * @param nSeverityLevel 鎶ヨ绛夌骇
+    * @param nDeviceId 璁惧ID
+    * @param nUnitId 鍗曞厓ID
+    * @param strDescription 鎻忚堪
+    * @param endTime 缁撴潫鏃堕棿
+    * @return 鎴愬姛杩斿洖true锛屽け璐ヨ繑鍥瀎alse
     */
     bool clearAlarmByAttributes(int nId, int nDeviceId, int nUnitId, const std::string& endTime);
 
 	/**
-	 * 读取报警文件
-	 * @param filename 文件名
-	 * @return 成功返回true,失败返回false
+	 * 璇诲彇鎶ヨ鏂囦欢
+	 * @param filename 鏂囦欢鍚�
+	 * @return 鎴愬姛杩斿洖true锛屽け璐ヨ繑鍥瀎alse
 	 */
     bool readAlarmFile(const std::string& filename);
 
     /**
-     * 保存报警文件
-     * @param filename 文件名
-     * @return 成功返回true,失败返回false
+     * 淇濆瓨鎶ヨ鏂囦欢
+     * @param filename 鏂囦欢鍚�
+     * @return 鎴愬姛杩斿洖true锛屽け璐ヨ繑鍥瀎alse
      */
     bool saveAlarmFile(const std::string& filename);
 
 	/**
-	 * 通过报警ID查询报警信息
-	 * @param nAlarmID 报警ID
-	 * @return 报警信息的指针
+	 * 閫氳繃鎶ヨID鏌ヨ鎶ヨ淇℃伅
+	 * @param nAlarmID 鎶ヨID
+	 * @return 鎶ヨ淇℃伅鐨勬寚閽�
 	 */
     const AlarmInfo* getAlarmInfoByID(int nAlarmID) const;
 
     /**
-    * 通过多个报警ID查询对应的报警信息
-    * @param alarmIDs 多个报警ID
-	* @return 返回多个报警信息
+    * 閫氳繃澶氫釜鎶ヨID鏌ヨ瀵瑰簲鐨勬姤璀︿俊鎭�
+    * @param alarmIDs 澶氫釜鎶ヨID
+	* @return 杩斿洖澶氫釜鎶ヨ淇℃伅
     */
     std::vector<AlarmInfo> getAlarmsInfoByIDs(const std::vector<int>& alarmIDs) const;
 
@@ -236,7 +237,7 @@
     AlarmManager();
     ~AlarmManager();
 
-    // 禁止拷贝和赋值
+    // 绂佹鎷疯礉鍜岃祴鍊�
     AlarmManager(const AlarmManager&) = delete;
     AlarmManager& operator=(const AlarmManager&) = delete;
 
diff --git a/SourceCode/Bond/Servo/AlarmPopupDlg.cpp b/SourceCode/Bond/Servo/AlarmPopupDlg.cpp
new file mode 100644
index 0000000..8f7b50a
--- /dev/null
+++ b/SourceCode/Bond/Servo/AlarmPopupDlg.cpp
@@ -0,0 +1,321 @@
+锘�#include "stdafx.h"
+#include "Servo.h"
+#include "AlarmPopupDlg.h"
+#include "afxdialogex.h"
+#include "Log.h"
+#include "Common.h"
+#include "HorizontalLine.h"
+#include "ServoDlg.h"
+
+
+IMPLEMENT_DYNAMIC(CAlarmPopupDlg, CDialogEx)
+
+CAlarmPopupDlg::CAlarmPopupDlg(CWnd* pParent /*=NULL*/)
+	: CDialogEx(IDD_DIALOG_POPUP_ALARM, pParent)
+	, m_hasActive(false)
+{
+	memset(&m_activeAlarm, 0, sizeof(m_activeAlarm));
+	m_crBkgnd = RGB(112, 146, 190);
+	m_hbrBkgnd = nullptr;
+}
+
+CAlarmPopupDlg::~CAlarmPopupDlg()
+{
+}
+
+void CAlarmPopupDlg::DoDataExchange(CDataExchange* pDX)
+{
+	CDialogEx::DoDataExchange(pDX);
+}
+
+BEGIN_MESSAGE_MAP(CAlarmPopupDlg, CDialogEx)
+	ON_BN_CLICKED(IDC_POPUP_BTN_CLOSE, &CAlarmPopupDlg::OnBnClickedClose)
+	ON_BN_CLICKED(IDC_BUTTON_ALARM_OFF, &CAlarmPopupDlg::OnBnClickedAlarmOff)
+	ON_BN_CLICKED(IDC_BUTTON_PREV, &CAlarmPopupDlg::OnBnClickedPrev)
+	ON_BN_CLICKED(IDC_BUTTON_NEXT, &CAlarmPopupDlg::OnBnClickedNext)
+	ON_WM_CTLCOLOR()
+	ON_WM_DESTROY()
+END_MESSAGE_MAP()
+
+BOOL CAlarmPopupDlg::OnInitDialog()
+{
+	CDialogEx::OnInitDialog();
+
+	// 鑳屾櫙鍒�
+	if (m_hbrBkgnd != nullptr) {
+		::DeleteObject(m_hbrBkgnd);
+	}
+	m_hbrBkgnd = CreateSolidBrush(m_crBkgnd);
+
+	// 瀛椾綋
+	HFONT hFontDefault = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
+	LOGFONT lf;
+
+	::GetObject(hFontDefault, sizeof(LOGFONT), &lf);
+	lf.lfHeight -= 8;
+	lf.lfWeight = 700;
+	m_fontTitle.CreateFontIndirect(&lf);
+
+	::GetObject(hFontDefault, sizeof(LOGFONT), &lf);
+	lf.lfHeight -= 8;
+	m_fontLevel.CreateFontIndirect(&lf);
+
+	::GetObject(hFontDefault, sizeof(LOGFONT), &lf);
+	lf.lfHeight -= 16;
+	m_fontName.CreateFontIndirect(&lf);
+
+	::GetObject(hFontDefault, sizeof(LOGFONT), &lf);
+	lf.lfHeight -= 8;
+	m_fontDescription.CreateFontIndirect(&lf);
+
+	GetDlgItem(IDC_LABEL_TITLE)->SetFont(&m_fontTitle);
+	GetDlgItem(IDC_LABEL_LEVEL)->SetFont(&m_fontLevel);
+	GetDlgItem(IDC_LABEL_NAME)->SetFont(&m_fontName);
+	GetDlgItem(IDC_LABEL_DESCRIPTION)->SetFont(&m_fontDescription);
+	GetDlgItem(IDC_LABEL_NO_ALARM)->SetFont(&m_fontDescription);
+
+
+	// 鍥炬爣
+	CString strIcon1;
+	HICON hIcon;
+	CStatic* pStatic;
+
+	strIcon1.Format(_T("%s\\Res\\Alarm_o_24.ico"), theApp.m_strAppDir);
+	pStatic = (CStatic*)GetDlgItem(IDC_ICON_TITLE);
+	hIcon = (HICON)::LoadImage(AfxGetInstanceHandle(),
+		strIcon1, IMAGE_ICON, 24, 24,
+		LR_LOADFROMFILE | LR_DEFAULTCOLOR | LR_CREATEDIBSECTION | LR_DEFAULTSIZE);
+	pStatic->SetIcon(hIcon);
+
+	strIcon1.Format(_T("%s\\Res\\Alarm_o_64.ico"), theApp.m_strAppDir);
+	pStatic = (CStatic*)GetDlgItem(IDC_ICON_ALARM);
+	hIcon = (HICON)::LoadImage(AfxGetInstanceHandle(),
+		strIcon1, IMAGE_ICON, 64, 64,
+		LR_LOADFROMFILE | LR_DEFAULTCOLOR | LR_CREATEDIBSECTION | LR_DEFAULTSIZE);
+	pStatic->SetIcon(hIcon);
+
+
+	// 鍏抽棴鎸夐挳
+	strIcon1.Format(_T("%s\\Res\\close_blcak_24.ico"), theApp.m_strAppDir);
+	pStatic = (CStatic*)GetDlgItem(IDC_ICON_ALARM);
+	hIcon = (HICON)::LoadImage(AfxGetInstanceHandle(),
+		strIcon1, IMAGE_ICON, 128, 128,
+		LR_LOADFROMFILE | LR_DEFAULTCOLOR | LR_CREATEDIBSECTION | LR_DEFAULTSIZE);
+	m_btnClose.SubclassDlgItem(IDC_POPUP_BTN_CLOSE, this);
+	m_btnClose.SetIcon(hIcon, hIcon, 24);
+	m_btnClose.SetFaceColor(m_crBkgnd);
+	m_btnClose.SetBkgndColor(BS_NORMAL, BTN_ALARM_OFF_BKGND_NORMAL);
+	m_btnClose.SetBkgndColor(BS_HOVER, BTN_ALARM_OFF_BKGND_NORMAL);
+	m_btnClose.SetBkgndColor(BS_PRESS, BTN_ALARM_OFF_BKGND_PRESS);
+	m_btnClose.SetFrameColor(m_crBkgnd);
+
+
+	// 瑙i櫎璀﹀憡鎸夐挳
+	m_btnAlarmOff.SubclassDlgItem(IDC_BUTTON_ALARM_OFF, this);
+	m_btnAlarmOff.SetFrameColor(BS_NORMAL, BTN_ALARM_OFF_FRAME_NORMAL);
+	m_btnAlarmOff.SetFrameColor(BS_HOVER, BTN_ALARM_OFF_FRAME_HOVER);
+	m_btnAlarmOff.SetFrameColor(BS_PRESS, BTN_ALARM_OFF_FRAME_PRESS);
+	m_btnAlarmOff.SetBkgndColor(BS_NORMAL, BTN_ALARM_OFF_BKGND_NORMAL);
+	m_btnAlarmOff.SetBkgndColor(BS_HOVER, BTN_ALARM_OFF_BKGND_HOVER);
+	m_btnAlarmOff.SetBkgndColor(BS_PRESS, BTN_ALARM_OFF_BKGND_PRESS);
+
+
+	// 闈欓煶鎸夐挳
+	bool bMute = false;
+	m_btnSoundOff.SubclassDlgItem(IDC_BUTTON_SOUND_OFF, this);
+	m_btnSoundOff.SetFrameColor(BS_NORMAL, BTN_SOUND_OFF_FRAME_NORMAL);
+	m_btnSoundOff.SetFrameColor(BS_HOVER, BTN_SOUND_OFF_FRAME_HOVER);
+	m_btnSoundOff.SetFrameColor(BS_PRESS, BTN_SOUND_OFF_FRAME_PRESS);
+	SetButtonBackgroundColors(bMute);
+
+	// 妯嚎1
+	CHorizontalLine* pLine = CHorizontalLine::Hook(GetDlgItem(IDC_LINE1)->m_hWnd);
+	pLine->SetBkgndColor(m_crBkgnd);
+	pLine->SetLineColor(RGB(168, 168, 168));
+
+	pLine = CHorizontalLine::Hook(GetDlgItem(IDC_LINE2)->m_hWnd);
+	pLine->SetBkgndColor(m_crBkgnd);
+	pLine->SetLineColor(RGB(168, 168, 168));
+
+	RefreshContent();
+	return TRUE;
+}
+
+void CAlarmPopupDlg::ShowNoAlarmControls(bool bShow)
+{
+	const int ids[] = { IDC_LABEL_NO_ALARM };
+	for (int id : ids) {
+		if (auto* p = GetDlgItem(id)) {
+			p->ShowWindow(bShow ? SW_SHOW : SW_HIDE);
+		}
+	}
+}
+
+void CAlarmPopupDlg::ShowAlarmControls(bool bShow)
+{
+	const int ids[] = {
+		IDC_BUTTON_PREV, IDC_BUTTON_NEXT,
+		IDC_LABEL_TITLE, IDC_ICON_ALARM, IDC_ICON_TITLE,
+		IDC_LABEL_LEVEL, IDC_LABEL_NAME,
+		IDC_LINE1, IDC_BUTTON_SOUND_OFF, IDC_BUTTON_ALARM_OFF,
+		IDC_LINE2, IDC_LABEL_DESCRIPTION
+	};
+	for (int id : ids) {
+		if (auto* p = GetDlgItem(id)) {
+			p->ShowWindow(bShow ? SW_SHOW : SW_HIDE);
+		}
+	}
+}
+
+void CAlarmPopupDlg::RefreshContent()
+{
+	m_activeAlarms = AlarmManager::getInstance().getActiveAlarms();
+	m_activeIndex = 0;
+
+	if (!m_activeAlarms.empty()) {
+		m_hasActive = true;
+		DisplayActiveAt(m_activeIndex);
+		ShowAlarmControls(true);
+		ShowNoAlarmControls(false);
+	}
+	else {
+		m_hasActive = false;
+		SetDlgItemText(IDC_LABEL_NO_ALARM, _T("褰撳墠鏃犳姤璀�"));
+		SetDlgItemText(IDC_LABEL_NAME, _T(""));
+		SetDlgItemText(IDC_LABEL_LEVEL, _T(""));
+		SetDlgItemText(IDC_LABEL_DESCRIPTION, _T(""));
+		ShowAlarmControls(false);
+		ShowNoAlarmControls(true);
+	}
+}
+
+void CAlarmPopupDlg::OnBnClickedClose()
+{
+	ShowWindow(SW_HIDE);
+}
+
+void CAlarmPopupDlg::OnBnClickedAlarmOff()
+{
+	if (!m_hasActive) return;
+
+	AlarmManager& alarmManager = AlarmManager::getInstance();
+	alarmManager.clearAlarmByAttributes(
+		m_activeAlarm.nId,
+		m_activeAlarm.nDeviceId,
+		m_activeAlarm.nUnitId,
+		CToolUnits::getCurrentTimeString());
+	RefreshContent();
+}
+
+void CAlarmPopupDlg::OnBnClickedPrev()
+{
+	if (m_activeIndex > 0) {
+		--m_activeIndex;
+		DisplayActiveAt(m_activeIndex);
+	}
+}
+
+void CAlarmPopupDlg::OnBnClickedNext()
+{
+	if (m_activeIndex + 1 < static_cast<int>(m_activeAlarms.size())) {
+		++m_activeIndex;
+		DisplayActiveAt(m_activeIndex);
+	}
+}
+
+void CAlarmPopupDlg::DisplayActiveAt(int idx)
+{
+	if (idx < 0 || idx >= static_cast<int>(m_activeAlarms.size())) return;
+
+	m_activeAlarm = m_activeAlarms[idx];
+
+	// 鏍囪宸茶
+	if (auto* pParent = dynamic_cast<CServoDlg*>(GetParent())) {
+		pParent->AckAlarm(m_activeAlarm.nId);
+	}
+
+	AlarmManager& alarmManager = AlarmManager::getInstance();
+	const AlarmInfo* info = alarmManager.getAlarmInfoByID(m_activeAlarm.nId);
+
+	CString title, level, name, desc;
+	level.Format(_T("绛夌骇: %d"), m_activeAlarm.nSeverityLevel);
+
+	if (info != nullptr && !info->strAlarmText.empty()) {
+		name = CString(info->strAlarmText.c_str());
+	}
+	else {
+		name.Format(_T("ID:%d (%s)"), m_activeAlarm.nId, CString(m_activeAlarm.strDeviceName.c_str()));
+	}
+
+	if (!m_activeAlarm.strDescription.empty()) {
+		desc = CString(m_activeAlarm.strDescription.c_str());
+	}
+	else if (info != nullptr && !info->strDescription.empty()) {
+		desc = CString(info->strDescription.c_str());
+	}
+	else {
+		desc = _T("鏆傛棤鎻忚堪");
+	}
+
+	title.Format(_T("璁惧:%s 鍗曞厓:%s"),
+		CString(m_activeAlarm.strDeviceName.c_str()),
+		CString(m_activeAlarm.strUnitName.c_str()));
+
+	SetDlgItemText(IDC_LABEL_TITLE, title);
+	SetDlgItemText(IDC_LABEL_NAME, name);
+	SetDlgItemText(IDC_LABEL_LEVEL, level);
+	SetDlgItemText(IDC_LABEL_DESCRIPTION, desc);
+
+	UpdateNavButtons();
+	ShowWindow(SW_SHOW);
+}
+
+void CAlarmPopupDlg::UpdateNavButtons()
+{
+	auto* pPrev = GetDlgItem(IDC_BUTTON_PREV);
+	auto* pNext = GetDlgItem(IDC_BUTTON_NEXT);
+	if (pPrev) pPrev->EnableWindow(m_activeIndex > 0);
+	if (pNext) pNext->EnableWindow(m_activeIndex + 1 < static_cast<int>(m_activeAlarms.size()));
+}
+
+HBRUSH CAlarmPopupDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
+{
+	HBRUSH hbr = CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);
+
+	if (nCtlColor == CTLCOLOR_STATIC) {
+		pDC->SetBkColor(m_crBkgnd);
+		pDC->SetTextColor(RGB(30, 30, 30));
+		hbr = m_hbrBkgnd;
+	}
+	else if (nCtlColor == CTLCOLOR_DLG) {
+		hbr = m_hbrBkgnd;
+	}
+
+	return hbr;
+}
+
+void CAlarmPopupDlg::OnDestroy()
+{
+	CDialogEx::OnDestroy();
+
+	if (m_hbrBkgnd != nullptr) {
+		::DeleteObject(m_hbrBkgnd);
+		m_hbrBkgnd = nullptr;
+	}
+}
+
+void CAlarmPopupDlg::SetButtonBackgroundColors(bool bMute)
+{
+	if (!bMute) {
+		m_btnSoundOff.SetBkgndColor(BS_NORMAL, BTN_SOUND_OFF_BKGND_NORMAL);
+		m_btnSoundOff.SetBkgndColor(BS_HOVER, BTN_SOUND_OFF_BKGND_HOVER);
+		m_btnSoundOff.SetBkgndColor(BS_PRESS, BTN_SOUND_OFF_BKGND_PRESS);
+		m_btnSoundOff.Invalidate();
+	}
+	else {
+		m_btnSoundOff.SetBkgndColor(BS_NORMAL, BTN_SOUND_ON_BKGND_NORMAL);
+		m_btnSoundOff.SetBkgndColor(BS_HOVER, BTN_SOUND_ON_BKGND_HOVER);
+		m_btnSoundOff.SetBkgndColor(BS_PRESS, BTN_SOUND_ON_BKGND_PRESS);
+		m_btnSoundOff.Invalidate();
+	}
+}
diff --git a/SourceCode/Bond/Servo/AlarmPopupDlg.h b/SourceCode/Bond/Servo/AlarmPopupDlg.h
new file mode 100644
index 0000000..9822f18
--- /dev/null
+++ b/SourceCode/Bond/Servo/AlarmPopupDlg.h
@@ -0,0 +1,59 @@
+锘�#pragma once
+#include "afxwin.h"
+#include "AlarmManager.h"
+#include "Common.h"
+#include "ToolUnits.h"
+#include "BlButton.h"
+
+// 绠�鍖栫増鎶ヨ寮圭獥锛屽鎺� AlarmManager 鐨勬椿璺冨憡璀�
+class CAlarmPopupDlg : public CDialogEx
+{
+	DECLARE_DYNAMIC(CAlarmPopupDlg)
+
+public:
+	CAlarmPopupDlg(CWnd* pParent = NULL);
+	virtual ~CAlarmPopupDlg();
+
+public:
+	void RefreshContent(); // 鍒锋柊褰撳墠鍛婅鏄剧ず
+
+private:
+	COLORREF m_crBkgnd;
+	HBRUSH m_hbrBkgnd;
+	CFont m_fontTitle;
+	CFont m_fontLevel;
+	CFont m_fontName;
+	CFont m_fontDescription;
+	CBlButton m_btnClose;
+	CBlButton m_btnSoundOff;
+	CBlButton m_btnAlarmOff;
+
+// 瀵硅瘽妗嗘暟鎹�
+#ifdef AFX_DESIGN_TIME
+	enum { IDD = IDD_DIALOG_POPUP_ALARM };
+#endif
+
+protected:
+	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 鏀寔
+
+	DECLARE_MESSAGE_MAP()
+public:
+	virtual BOOL OnInitDialog();
+	afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
+	afx_msg void OnDestroy();
+	afx_msg void OnBnClickedClose();
+	afx_msg void OnBnClickedAlarmOff();
+	afx_msg void OnBnClickedPrev();
+	afx_msg void OnBnClickedNext();
+
+private:
+	bool m_hasActive;
+	AlarmData m_activeAlarm;
+	std::vector<AlarmData> m_activeAlarms;
+	int m_activeIndex = 0;
+	void SetButtonBackgroundColors(bool bMute);
+	void ShowNoAlarmControls(bool bShow);
+	void ShowAlarmControls(bool bShow);
+	void UpdateNavButtons();
+	void DisplayActiveAt(int idx);
+};
diff --git a/SourceCode/Bond/Servo/CBonder.cpp b/SourceCode/Bond/Servo/CBonder.cpp
index b9f8343..4bccfbc 100644
--- a/SourceCode/Bond/Servo/CBonder.cpp
+++ b/SourceCode/Bond/Servo/CBonder.cpp
@@ -1,4 +1,4 @@
-#include "stdafx.h"
+锘�#include "stdafx.h"
 #include "CBonder.h"
 
 
@@ -29,10 +29,10 @@
 		CEquipment::term();
 	}
 
-	// 必须要实现的虚函数,在此初始化Pin列表
+	// 蹇呴』瑕佸疄鐜扮殑铏氬嚱鏁帮紝鍦ㄦ鍒濆鍖朠in鍒楄〃
 	void CBonder::initPins()
 	{
-		// 加入Pin初始化代码
+		// 鍔犲叆Pin鍒濆鍖栦唬鐮�
 		LOGI("<CBonder>initPins");
 		addPin(SERVO::PinType::INPUT, _T("In1"));
 		addPin(SERVO::PinType::INPUT, _T("In2"));
@@ -137,8 +137,8 @@
 
 		{
 			// CIM Message Confirm
-			// 要将int32的值拆分为两个short, 分别为msg id和panel id
-			// 65538, 2为msg id, 1为panel id
+			// 瑕佸皢int32鐨勫�兼媶鍒嗕负涓や釜short, 鍒嗗埆涓簃sg id鍜宲anel id
+			// 65538, 2涓簃sg id, 1涓簆anel id
 			CEqReadIntStep* pStep = new CEqReadIntStep(__INT32, m_nIndex == 0 ? 0x9d80 : 0xdd80);
 			pStep->setName(STEP_EQ_CIM_MESSAGE_CONFIRM);
 			pStep->setWriteSignalDev(m_nIndex == 0 ? 0x349 : 0x649);
@@ -178,7 +178,7 @@
 		}
 
 		{
-			// 请求主配方列表的step
+			// 璇锋眰涓婚厤鏂瑰垪琛ㄧ殑step
 			CEqWriteStep* pStep = new CEqWriteStep();
 			pStep->setName(STEP_EQ_MASTER_RECIPE_LIST_REQ);
 			pStep->setWriteSignalDev(m_nIndex == 0 ? 0x366 : 0x666);
@@ -195,7 +195,7 @@
 					CEqReadStep* pTmpStep = (CEqReadStep*)pFrom;
 					short ret = MRLRC_OK;
 					if (code == ROK && pszData != nullptr && size > 0) {
-						// 此处解释配方数据
+						// 姝ゅ瑙i噴閰嶆柟鏁版嵁
 						ret = decodeRecipeListReport(pszData, size);
 					}
 					pTmpStep->setReturnCode(ret);
@@ -210,7 +210,7 @@
 		}
 
 		{
-			// 请求配方参数
+			// 璇锋眰閰嶆柟鍙傛暟
 			CEqWriteStep* pStep = new CEqWriteStep();
 			pStep->setName(STEP_EQ_RECIPE_PARAMETER_REQ);
 			pStep->setWriteSignalDev(m_nIndex == 0 ? 0x367 : 0x667);
@@ -227,7 +227,7 @@
 					CEqReadStep* pTmpStep = (CEqReadStep*)pFrom;
 					short ret = MRLRC_OK;
 					if (code == ROK && pszData != nullptr && size > 0) {
-						// 此处解释配方数据
+						// 姝ゅ瑙i噴閰嶆柟鏁版嵁
 						ret = decodeRecipeParameterReport(pszData, size);
 					}
 					pTmpStep->setReturnCode(ret);
@@ -241,7 +241,7 @@
 			}
 		}
 
-		// 使用CEqReadStep替换CEqJobEventStep
+		// 浣跨敤CEqReadStep鏇挎崲CEqJobEventStep
 		{
 			// Received Job Report Upstream #1~9
 			char szBuffer[256];
@@ -404,7 +404,7 @@
 		}
 	}
 
-	// 必须要实现的虚函数,在此初始化Slot信息
+	// 蹇呴』瑕佸疄鐜扮殑铏氬嚱鏁帮紝鍦ㄦ鍒濆鍖朣lot淇℃伅
 	void CBonder::initSlots()
 	{
 		m_slot[0].enable();
@@ -487,35 +487,35 @@
 		return 0;
 	}
 
-	int CBonder::onProcessStateChanged(int slotNo, PROCESS_STATE state)
+	int CBonder::onProcessStateChanged(int slotNo, PROCESS_STATE prevState, PROCESS_STATE state)
 	{
-		CEquipment::onProcessStateChanged(slotNo, state);
+		CEquipment::onProcessStateChanged(slotNo, prevState, state);
 
 		if (state == PROCESS_STATE::Complete) {
-			// 检查数据,当前两片玻璃,一片为G1, 一片为G2, 且pProcessData中的id能匹配G1或G2
+			// 妫�鏌ユ暟鎹紝褰撳墠涓ょ墖鐜荤拑锛屼竴鐗囦负G1, 涓�鐗囦负G2, 涓攑ProcessData涓殑id鑳藉尮閰岹1鎴朑2
 			Lock();
 			CGlass* pGlass2 = getGlassFromSlot(1);
 			CGlass* pGlass1 = getGlassFromSlot(2);
 			if (pGlass1 == nullptr || pGlass2 == nullptr) {
-				LOGE("<CBonder-%s>onProcessData,错误!不满足两片玻璃且分别为G1与G2的条件,请检查数据是否正确!", m_strName.c_str());
+				LOGE("<CBonder-%s>onProcessData,閿欒!涓嶆弧瓒充袱鐗囩幓鐠冧笖鍒嗗埆涓篏1涓嶨2鐨勬潯浠讹紝璇锋鏌ユ暟鎹槸鍚︽纭�!", m_strName.c_str());
 				Unlock();
 				return -1;
 			}
 			if (pGlass1->getBuddy() != nullptr) {
-				LOGE("<CBonder-%s>onProcessData,错误!玻璃较早前已被绑定,请检查数据是否正确!", m_strName.c_str());
+				LOGE("<CBonder-%s>onProcessData,閿欒!鐜荤拑杈冩棭鍓嶅凡琚粦瀹氾紝璇锋鏌ユ暟鎹槸鍚︽纭�!", m_strName.c_str());
 				Unlock();
 				return -1;
 			}
 
 			if (pGlass1->getType() != MaterialsType::G1 || pGlass2->getType() != MaterialsType::G2) {
-				LOGE("<CBonder-%s>onProcessData,错误!两片玻璃未匹配,必须分别为G1和G2类型,请检查数据是否正确!", m_strName.c_str());
+				LOGE("<CBonder-%s>onProcessData,閿欒!涓ょ墖鐜荤拑鏈尮閰嶏紝蹇呴』鍒嗗埆涓篏1鍜孏2绫诲瀷锛岃妫�鏌ユ暟鎹槸鍚︽纭�!", m_strName.c_str());
 				Unlock();
 				return -1;
 			}
 
 			pGlass1->setBuddy(pGlass2);
 			getSlot(0)->setContext(nullptr);
-			LOGE("<CBonder-%s>onProcessStateChanged,%s和%s已贴合!", m_strName.c_str(),
+			LOGE("<CBonder-%s>onProcessStateChanged,%s鍜�%s宸茶创鍚�!", m_strName.c_str(),
 				pGlass1->getID().c_str(), pGlass2->getID().c_str());
 			Unlock();
 		}
@@ -535,112 +535,112 @@
 		int i = 0, v;
 
 
-		// 1.校正对位延时
+		// 1.鏍℃瀵逛綅寤舵椂
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("校正对位延时", "", this->getName().c_str(), v * 0.01f));
+		params.push_back(CParam("鏍℃瀵逛綅寤舵椂", "", this->getName().c_str(), v * 0.01f));
 		i += 2;
 
-		// 2.保压时间
+		// 2.淇濆帇鏃堕棿
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("保压时间", "", this->getName().c_str(), v * 0.01f));
+		params.push_back(CParam("淇濆帇鏃堕棿", "", this->getName().c_str(), v * 0.01f));
 		i += 2;
 
-		// 3.腔体破真空延时
+		// 3.鑵斾綋鐮寸湡绌哄欢鏃�
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("腔体破真空延时", "", this->getName().c_str(), v * 0.01f));
+		params.push_back(CParam("鑵斾綋鐮寸湡绌哄欢鏃�", "", this->getName().c_str(), v * 0.01f));
 		i += 2;
 
-		// 4.腔体分子泵启动延时
+		// 4.鑵斾綋鍒嗗瓙娉靛惎鍔ㄥ欢鏃�
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("腔体分子泵启动延时", "", this->getName().c_str(), v * 0.1f));
+		params.push_back(CParam("鑵斾綋鍒嗗瓙娉靛惎鍔ㄥ欢鏃�", "", this->getName().c_str(), v * 0.1f));
 		i += 2;
 
-		// 5.腔体贴附抽真空延时
+		// 5.鑵斾綋璐撮檮鎶界湡绌哄欢鏃�
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("腔体贴附抽真空延时", "", this->getName().c_str(), v * 0.1f));
+		params.push_back(CParam("鑵斾綋璐撮檮鎶界湡绌哄欢鏃�", "", this->getName().c_str(), v * 0.1f));
 		i += 2;
 
-		// 6.加热等待延时
+		// 6.鍔犵儹绛夊緟寤舵椂
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("加热等待延时", "", this->getName().c_str(), v * 0.1f));
+		params.push_back(CParam("鍔犵儹绛夊緟寤舵椂", "", this->getName().c_str(), v * 0.1f));
 		i += 2;
 
-		// 7.气囊压力设定
+		// 7.姘斿泭鍘嬪姏璁惧畾
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
-		params.push_back(CParam("气囊压力设定", "", this->getName().c_str(), v * 0.001f));
+		params.push_back(CParam("姘斿泭鍘嬪姏璁惧畾", "", this->getName().c_str(), v * 0.001f));
 		i += 4;
 
-		// 8.气囊加压速率
+		// 8.姘斿泭鍔犲帇閫熺巼
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
-		params.push_back(CParam("气囊加压速率", "", this->getName().c_str(), v * 0.001f));
+		params.push_back(CParam("姘斿泭鍔犲帇閫熺巼", "", this->getName().c_str(), v * 0.001f));
 		i += 4;
 
-		// 9.气囊泄压速率
+		// 9.姘斿泭娉勫帇閫熺巼
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
-		params.push_back(CParam("气囊泄压速率", "", this->getName().c_str(), v * 0.001f));
+		params.push_back(CParam("姘斿泭娉勫帇閫熺巼", "", this->getName().c_str(), v * 0.001f));
 		i += 4;
 
-		// 10.贴附压力上限
+		// 10.璐撮檮鍘嬪姏涓婇檺
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
-		params.push_back(CParam("贴附压力上限", "", this->getName().c_str(), v * 0.1f));
+		params.push_back(CParam("璐撮檮鍘嬪姏涓婇檺", "", this->getName().c_str(), v * 0.1f));
 		i += 4;
 
-		// 11.Z轴转矩速度设定
+		// 11.Z杞磋浆鐭╅�熷害璁惧畾
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
-		params.push_back(CParam("上腔Z轴转矩速度设定", "", this->getName().c_str(), v * 0.001f));
+		params.push_back(CParam("涓婅厰Z杞磋浆鐭╅�熷害璁惧畾", "", this->getName().c_str(), v * 0.001f));
 		i += 4;
 
-		// 12.上腔温度设定
+		// 12.涓婅厰娓╁害璁惧畾
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("上腔温度设定", "", this->getName().c_str(), v * 0.1f));
+		params.push_back(CParam("涓婅厰娓╁害璁惧畾", "", this->getName().c_str(), v * 0.1f));
 		i += 2;
 
-		// 13.下腔温度设定
+		// 13.涓嬭厰娓╁害璁惧畾
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("下腔温度设定", "", this->getName().c_str(), v * 0.1f));
+		params.push_back(CParam("涓嬭厰娓╁害璁惧畾", "", this->getName().c_str(), v * 0.1f));
 		i += 2;
 
-		// 14.上腔Z轴预贴合位速度
+		// 14.涓婅厰Z杞撮璐村悎浣嶉�熷害
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
-		params.push_back(CParam("上腔Z轴预贴合位速度", "", this->getName().c_str(), v * 0.001f));
+		params.push_back(CParam("涓婅厰Z杞撮璐村悎浣嶉�熷害", "", this->getName().c_str(), v * 0.001f));
 		i += 4;
 
-		// 15.上腔Z轴贴附位速度
+		// 15.涓婅厰Z杞磋创闄勪綅閫熷害
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
-		params.push_back(CParam("上腔Z轴贴附位速度", "", this->getName().c_str(), v * 0.001f));
+		params.push_back(CParam("涓婅厰Z杞磋创闄勪綅閫熷害", "", this->getName().c_str(), v * 0.001f));
 		i += 4;
 
-		// 16.上腔Z上腔加热位间距
+		// 16.涓婅厰Z涓婅厰鍔犵儹浣嶉棿璺�
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
-		params.push_back(CParam("上腔Z上腔加热位间距", "", this->getName().c_str(), v * 0.001f));
+		params.push_back(CParam("涓婅厰Z涓婅厰鍔犵儹浣嶉棿璺�", "", this->getName().c_str(), v * 0.001f));
 		i += 4;
 
-		// 17.上腔贴附位压入量
+		// 17.涓婅厰璐撮檮浣嶅帇鍏ラ噺
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
-		params.push_back(CParam("上腔贴附位压入量", "", this->getName().c_str(), v * 0.001f));
+		params.push_back(CParam("涓婅厰璐撮檮浣嶅帇鍏ラ噺", "", this->getName().c_str(), v * 0.001f));
 		i += 4;
 
-		// 18.上腔Z轴破真空距离
+		// 18.涓婅厰Z杞寸牬鐪熺┖璺濈
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
-		params.push_back(CParam("上腔Z轴破真空距离", "", this->getName().c_str(), v * 0.001f));
+		params.push_back(CParam("涓婅厰Z杞寸牬鐪熺┖璺濈", "", this->getName().c_str(), v * 0.001f));
 		i += 4;
 
-		// 19.下顶Pin破真空距离
+		// 19.涓嬮《Pin鐮寸湡绌鸿窛绂�
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
-		params.push_back(CParam("下顶Pin破真空距离", "", this->getName().c_str(), v * 0.001f));
+		params.push_back(CParam("涓嬮《Pin鐮寸湡绌鸿窛绂�", "", this->getName().c_str(), v * 0.001f));
 		i += 4;
 
-		// 20.下顶Pin加热位间距
+		// 20.涓嬮《Pin鍔犵儹浣嶉棿璺�
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
-		params.push_back(CParam("下顶Pin加热位间距", "", this->getName().c_str(), v * 0.001f));
+		params.push_back(CParam("涓嬮《Pin鍔犵儹浣嶉棿璺�", "", this->getName().c_str(), v * 0.001f));
 		i += 4;
 
-		// 21.腔体真空泵真空规设定值
-		params.push_back(CParam("腔体真空泵真空规设定值", "", this->getName().c_str(), (double)toFloat(&pszData[i])));
+		// 21.鑵斾綋鐪熺┖娉电湡绌鸿璁惧畾鍊�
+		params.push_back(CParam("鑵斾綋鐪熺┖娉电湡绌鸿璁惧畾鍊�", "", this->getName().c_str(), (double)toFloat(&pszData[i])));
 		i += 4;
 
-		// 22.腔体分子泵到达设定值
-		params.push_back(CParam("腔体分子泵到达设定值", "", this->getName().c_str(), (double)toFloat(&pszData[i])));
+		// 22.鑵斾綋鍒嗗瓙娉靛埌杈捐瀹氬��
+		params.push_back(CParam("鑵斾綋鍒嗗瓙娉靛埌杈捐瀹氬��", "", this->getName().c_str(), (double)toFloat(&pszData[i])));
 		i += 4;
 
 
@@ -655,24 +655,24 @@
 	int CBonder::parsingSVData(const char* pszData, size_t size, std::vector<CParam>& params)
 	{
 		/*
-		1	工艺运行步骤	1Word	123456
-			2	气囊压力当前	2Word	12345.6
-			3	上腔压力合计	1Word	1234.56
-			4	管道真空规值	FLOAT	123.456
-			5	腔体真空规值	FLOAT	123.456
-			6	上腔温度1	1Word	12345.6
-			7	上腔温度2	1Word	12345.6
-			8	上腔温度3	1Word	12345.6
-			9	上腔温度4	1Word	12345.6
-			10	上腔温度5	1Word	12345.6
-			11	上腔温度6	1Word	12345.6
-			12	下腔温度1	1Word	12345.6
-			13	下腔温度2	1Word	12345.6
-			14	下腔温度3	1Word	12345.6
-			15	下腔温度4	1Word	12345.6
-			16	下腔温度5	1Word	12345.6
-			17	下腔温度6	1Word	12345.6
-			18	压合剩余时间	1Word	1234.56
+		1	宸ヨ壓杩愯姝ラ	1Word	123456
+			2	姘斿泭鍘嬪姏褰撳墠	2Word	12345.6
+			3	涓婅厰鍘嬪姏鍚堣	1Word	1234.56
+			4	绠¢亾鐪熺┖瑙勫��	FLOAT	123.456
+			5	鑵斾綋鐪熺┖瑙勫��	FLOAT	123.456
+			6	涓婅厰娓╁害1	1Word	12345.6
+			7	涓婅厰娓╁害2	1Word	12345.6
+			8	涓婅厰娓╁害3	1Word	12345.6
+			9	涓婅厰娓╁害4	1Word	12345.6
+			10	涓婅厰娓╁害5	1Word	12345.6
+			11	涓婅厰娓╁害6	1Word	12345.6
+			12	涓嬭厰娓╁害1	1Word	12345.6
+			13	涓嬭厰娓╁害2	1Word	12345.6
+			14	涓嬭厰娓╁害3	1Word	12345.6
+			15	涓嬭厰娓╁害4	1Word	12345.6
+			16	涓嬭厰娓╁害5	1Word	12345.6
+			17	涓嬭厰娓╁害6	1Word	12345.6
+			18	鍘嬪悎鍓╀綑鏃堕棿	1Word	1234.56
 */
 
 		ASSERT(pszData);
@@ -680,97 +680,97 @@
 		int i = 0, v;
 
 
-		// 1.工艺运行步骤
+		// 1.宸ヨ壓杩愯姝ラ
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("工艺运行步骤", "", this->getName().c_str(), v));
+		params.push_back(CParam("宸ヨ壓杩愯姝ラ", "", this->getName().c_str(), v));
 		i += 2;
 
-		// 2.气囊压力当前
+		// 2.姘斿泭鍘嬪姏褰撳墠
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
-		params.push_back(CParam("气囊压力当前", "", this->getName().c_str(), v * 0.1f));
+		params.push_back(CParam("姘斿泭鍘嬪姏褰撳墠", "", this->getName().c_str(), v * 0.1f));
 		i += 4;
 
-		// 3.上腔压力合计
+		// 3.涓婅厰鍘嬪姏鍚堣
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("上腔压力合计", "", this->getName().c_str(), ((short)v) * 0.01f));
+		params.push_back(CParam("涓婅厰鍘嬪姏鍚堣", "", this->getName().c_str(), ((short)v) * 0.01f));
 		i += 2;
 
-		// 4.管道真空规值
-		params.push_back(CParam("管道真空规值", "", this->getName().c_str(), (double)toFloat(&pszData[i])));
+		// 4.绠¢亾鐪熺┖瑙勫��
+		params.push_back(CParam("绠¢亾鐪熺┖瑙勫��", "", this->getName().c_str(), (double)toFloat(&pszData[i])));
 		i += 4;
 
-		// 5.腔体真空规值
-		params.push_back(CParam("腔体真空规值", "", this->getName().c_str(), (double)toFloat(&pszData[i])));
+		// 5.鑵斾綋鐪熺┖瑙勫��
+		params.push_back(CParam("鑵斾綋鐪熺┖瑙勫��", "", this->getName().c_str(), (double)toFloat(&pszData[i])));
 		i += 4;
 
-		// 6.上腔温度1
+		// 6.涓婅厰娓╁害1
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("上腔温度1", "", this->getName().c_str(), v * 0.1f));
+		params.push_back(CParam("涓婅厰娓╁害1", "", this->getName().c_str(), v * 0.1f));
 		i += 2;
 
-		// 7.上腔温度2
+		// 7.涓婅厰娓╁害2
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("上腔温度2", "", this->getName().c_str(), v * 0.1f));
+		params.push_back(CParam("涓婅厰娓╁害2", "", this->getName().c_str(), v * 0.1f));
 		i += 2;
 
-		// 8.上腔温度3
+		// 8.涓婅厰娓╁害3
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("上腔温度3", "", this->getName().c_str(), v * 0.1f));
+		params.push_back(CParam("涓婅厰娓╁害3", "", this->getName().c_str(), v * 0.1f));
 		i += 2;
 
-		// 9.上腔温度4
+		// 9.涓婅厰娓╁害4
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("上腔温度4", "", this->getName().c_str(), v * 0.1f));
+		params.push_back(CParam("涓婅厰娓╁害4", "", this->getName().c_str(), v * 0.1f));
 		i += 2;
 
-		// 10.上腔温度5
+		// 10.涓婅厰娓╁害5
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("上腔温度5", "", this->getName().c_str(), v * 0.1f));
+		params.push_back(CParam("涓婅厰娓╁害5", "", this->getName().c_str(), v * 0.1f));
 		i += 2;
 
-		// 11.上腔温度6
+		// 11.涓婅厰娓╁害6
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("上腔温度6", "", this->getName().c_str(), v * 0.1f));
+		params.push_back(CParam("涓婅厰娓╁害6", "", this->getName().c_str(), v * 0.1f));
 		i += 2;
 
-		// 12.下腔温度1
+		// 12.涓嬭厰娓╁害1
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("下腔温度1", "", this->getName().c_str(), v * 0.1f));
+		params.push_back(CParam("涓嬭厰娓╁害1", "", this->getName().c_str(), v * 0.1f));
 		i += 2;
 
-		// 13.下腔温度2
+		// 13.涓嬭厰娓╁害2
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("下腔温度2", "", this->getName().c_str(), v * 0.1f));
+		params.push_back(CParam("涓嬭厰娓╁害2", "", this->getName().c_str(), v * 0.1f));
 		i += 2;
 
-		// 14.下腔温度3
+		// 14.涓嬭厰娓╁害3
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("下腔温度3", "", this->getName().c_str(), v * 0.1f));
+		params.push_back(CParam("涓嬭厰娓╁害3", "", this->getName().c_str(), v * 0.1f));
 		i += 2;
 
-		// 15.下腔温度4
+		// 15.涓嬭厰娓╁害4
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("下腔温度4", "", this->getName().c_str(), v * 0.1f));
+		params.push_back(CParam("涓嬭厰娓╁害4", "", this->getName().c_str(), v * 0.1f));
 		i += 2;
 
-		// 16.下腔温度5
+		// 16.涓嬭厰娓╁害5
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("下腔温度5", "", this->getName().c_str(), v * 0.1f));
+		params.push_back(CParam("涓嬭厰娓╁害5", "", this->getName().c_str(), v * 0.1f));
 		i += 2;
 
-		// 17.下腔温度6
+		// 17.涓嬭厰娓╁害6
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("下腔温度6", "", this->getName().c_str(), v * 0.1f));
+		params.push_back(CParam("涓嬭厰娓╁害6", "", this->getName().c_str(), v * 0.1f));
 		i += 2;
 
-		// 18.加热剩余时间
+		// 18.鍔犵儹鍓╀綑鏃堕棿
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("加热剩余时间", "", this->getName().c_str(), v * 0.01f));
+		params.push_back(CParam("鍔犵儹鍓╀綑鏃堕棿", "", this->getName().c_str(), v * 0.01f));
 		i += 2;
 
-		// 19.压合剩余时间
+		// 19.鍘嬪悎鍓╀綑鏃堕棿
 		v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
-		params.push_back(CParam("压合剩余时间", "", this->getName().c_str(), v * 0.01f));
+		params.push_back(CParam("鍘嬪悎鍓╀綑鏃堕棿", "", this->getName().c_str(), v * 0.01f));
 		i += 2;
 
 		return (int)params.size();
diff --git a/SourceCode/Bond/Servo/CBonder.h b/SourceCode/Bond/Servo/CBonder.h
index 89631dc..e9a68e0 100644
--- a/SourceCode/Bond/Servo/CBonder.h
+++ b/SourceCode/Bond/Servo/CBonder.h
@@ -22,7 +22,7 @@
         virtual void getAttributeVector(CAttributeVector& attrubutes);
         virtual int recvIntent(CPin* pPin, CIntent* pIntent);
         virtual int onProcessData(CProcessData* pProcessData);
-        virtual int onProcessStateChanged(int slotNo, PROCESS_STATE state);
+        virtual int onProcessStateChanged(int slotNo, PROCESS_STATE prevState, PROCESS_STATE state);
         virtual int getIndexerOperationModeBaseValue();
         virtual int parsingParams(const char* pszData, size_t size, std::vector<CParam>& parsms);
         virtual int parsingProcessData(const char* pszData, size_t size, std::vector<CParam>& parsms);
diff --git a/SourceCode/Bond/Servo/CCollectionEvent.cpp b/SourceCode/Bond/Servo/CCollectionEvent.cpp
index ce5d1a2..13e23b2 100644
--- a/SourceCode/Bond/Servo/CCollectionEvent.cpp
+++ b/SourceCode/Bond/Servo/CCollectionEvent.cpp
@@ -59,9 +59,9 @@
 		}
 	}
 
-	unsigned int CCollectionEvent::getFirstPortID()
+	unsigned int CCollectionEvent::getFirstReportID()
 	{
-		if (m_reports.empty()) return -1;
+		if (m_reports.empty()) return 0;
 		return m_reports.front()->getReportId();
 	}
 
diff --git a/SourceCode/Bond/Servo/CCollectionEvent.h b/SourceCode/Bond/Servo/CCollectionEvent.h
index a0b023d..7dfe985 100644
--- a/SourceCode/Bond/Servo/CCollectionEvent.h
+++ b/SourceCode/Bond/Servo/CCollectionEvent.h
@@ -2,7 +2,6 @@
 #include "CReport.h"
 #include <vector>
 
-
 namespace SERVO {
 	class CCollectionEvent
 	{
@@ -17,13 +16,14 @@
 		std::string& getDescription();
 		std::vector<CReport*>& getReports();
 		std::string getReportIdsText();
+		const std::vector<unsigned int>& getReportIds() const { return m_rptids; }
 		BOOL addReport(CReport* pReport);
 		BOOL deleteReport(unsigned int nReportId);
 		CReport* getReport(unsigned int nReportId);
 
 		/* 如果一个CEID只有一个Report的场景,调用此函数设置或取消 */
 		void setReport(CReport* pReport);
-		unsigned int getFirstPortID();
+		unsigned int getFirstReportID();
 		CReport* getFirstReport();
 
 	private:
@@ -33,4 +33,4 @@
 		std::vector<unsigned int> m_rptids;
 		std::vector<CReport*> m_reports;
 	};
-}
+}
\ No newline at end of file
diff --git a/SourceCode/Bond/Servo/CControlJobManagerDlg.cpp b/SourceCode/Bond/Servo/CControlJobManagerDlg.cpp
index 2e2d92c..78036f4 100644
--- a/SourceCode/Bond/Servo/CControlJobManagerDlg.cpp
+++ b/SourceCode/Bond/Servo/CControlJobManagerDlg.cpp
@@ -534,15 +534,6 @@
 	m_pControlJob->setPJs(pjs);
 	m_pControlJob->clearIssues();
 	int nRet = master.setProcessJobs(pjs);
-
-	// 娌℃湁闂鐨刾j瑕侀噴鏀�
-	for (auto pj : pjs) {
-		if (!pj->issues().empty()) {
-			delete pj;
-		}
-	}
-	pjs.clear();
-
 	if (nRet <= 0) {
 		std::string msg("鍚屾Process Job澶辫触!");
 		for (auto pj : pjs) {
@@ -559,11 +550,20 @@
 					msg.append("\n");
 				}
 			}
+			delete pj;
 		}
+		pjs.clear();
 		AfxMessageBox(msg.c_str());
-
 		return;
 	}
+
+	// 缁х画閲婃斁鏈夐棶棰樼殑 ProcessJob
+	for (auto pj : pjs) {
+		if (!pj->issues().empty()) {
+			delete pj;
+		}
+	}
+	pjs.clear();
 
 	nRet = master.setControlJob(*m_pControlJob);
 	if (nRet != 0) {
@@ -592,55 +592,55 @@
 		SERVO::CLoadPort* pLoadPort = pPorts[m_pjWarps[p].port];
 		for (int i = 0; i < SLOT_MAX; ++i) {
 			SERVO::CSlot* pSlot = pLoadPort->getSlot(i);
-			if (!pSlot) {
-				continue;
-			}
-			
+			if (!pSlot) continue;
+
+			SERVO::CGlass* pGlass = dynamic_cast<SERVO::CGlass*>(pSlot->getContext());
+			if (pGlass == nullptr) continue;
+
+			SERVO::CJobDataS* pJobDataS = pGlass->getJobDataS();
+			if (pJobDataS == nullptr) continue;
+
+
 			// 璁剧疆 Panel ID 鍜屽嬀閫夋
 			SERVO::CProcessJob* pj = (SERVO::CProcessJob*)m_pjWarps[p].pj;
 			int nRecipeID = RecipeManager::getInstance().getIdByPPID(pj->recipeSpec());
 			RecipeInfo stRecipeInfo = RecipeManager::getInstance().getRecipeByPPID(pj->recipeSpec());
 			std::vector<DeviceRecipe> vecRecipeInfo = stRecipeInfo.vecDeviceList;
-			SERVO::CGlass* pGlass = dynamic_cast<SERVO::CGlass*>(pSlot->getContext());
-			SERVO::CJobDataS* pJobDataS = pGlass->getJobDataS();
-			if (pGlass != nullptr && pJobDataS != nullptr) {
-				pGlass->setScheduledForProcessing(m_pjWarps[p].checkSlot[i]);
-				pGlass->setType(static_cast<SERVO::MaterialsType>(m_pjWarps[p].material[i]));
 
-				SERVO::CJobDataS* pJobDataS = pGlass->getJobDataS();
-				pJobDataS->setLotId(pj->getLotId().c_str());
+			pGlass->setScheduledForProcessing(m_pjWarps[p].checkSlot[i]);
+			pGlass->setType(static_cast<SERVO::MaterialsType>(m_pjWarps[p].material[i]));
+
+			pJobDataS->setLotId(pj->getLotId().c_str());
 				pJobDataS->setProductId(pj->getProductId().c_str());
 				pJobDataS->setOperationId(pj->getOperationId().c_str());
 				pJobDataS->setMaterialsType(m_pjWarps[p].material[i]);
 				pJobDataS->setMasterRecipe(nRecipeID);
 
-				for (const auto& info : vecRecipeInfo) {
-					const std::string& name = info.strDeviceName;
-					short nRecipeID = (short)info.nRecipeID;
+			for (const auto& info : vecRecipeInfo) {
+				const std::string& name = info.strDeviceName;
+				short nRecipeID = (short)info.nRecipeID;
 				
-					if (name == EQ_NAME_EFEM) {
-						pJobDataS->setDeviceRecipeId(0, nRecipeID);
-					}
-					else if (name == EQ_NAME_BONDER1) {
-						pJobDataS->setDeviceRecipeId(1, nRecipeID);
-					}
-					else if (name == EQ_NAME_BONDER2) {
-						pJobDataS->setDeviceRecipeId(2, nRecipeID);
-					}
-					else if (name == EQ_NAME_BAKE_COOLING) {
-						pJobDataS->setDeviceRecipeId(3, nRecipeID);
-					}
-					else if (name == EQ_NAME_VACUUMBAKE) {
-						pJobDataS->setDeviceRecipeId(4, nRecipeID);
-					}
-					else if (name == EQ_NAME_MEASUREMENT) {
-						pJobDataS->setDeviceRecipeId(5, nRecipeID);
-					}
+				if (name == EQ_NAME_EFEM) {
+					pJobDataS->setDeviceRecipeId(0, nRecipeID);
+				}
+				else if (name == EQ_NAME_BONDER1) {
+					pJobDataS->setDeviceRecipeId(1, nRecipeID);
+				}
+				else if (name == EQ_NAME_BONDER2) {
+					pJobDataS->setDeviceRecipeId(2, nRecipeID);
+				}
+				else if (name == EQ_NAME_BAKE_COOLING) {
+					pJobDataS->setDeviceRecipeId(3, nRecipeID);
+				}
+				else if (name == EQ_NAME_VACUUMBAKE) {
+					pJobDataS->setDeviceRecipeId(4, nRecipeID);
+				}
+				else if (name == EQ_NAME_MEASUREMENT) {
+					pJobDataS->setDeviceRecipeId(5, nRecipeID);
 				}
 			}
 		}
 	}
-
 
 	// process start
 	for (int p = 0; p < 4; p++) {
diff --git a/SourceCode/Bond/Servo/CDataVariable.h b/SourceCode/Bond/Servo/CDataVariable.h
new file mode 100644
index 0000000..0452da1
--- /dev/null
+++ b/SourceCode/Bond/Servo/CDataVariable.h
@@ -0,0 +1,13 @@
+锘�#pragma once
+
+#include "CVariable.h"
+
+namespace SERVO {
+	// Data Variable:璇箟鍚� CVariable锛屼絾鐢ㄤ簬 DVID锛堟暟鎹彉閲忥級闆嗗悎銆�
+	class CDataVariable : public CVariable
+	{
+	public:
+		using CVariable::CVariable; // 澶嶇敤鍩虹被鏋勯��
+		~CDataVariable() = default;
+	};
+}
diff --git a/SourceCode/Bond/Servo/CEquipment.cpp b/SourceCode/Bond/Servo/CEquipment.cpp
index f9d930a..36217c3 100644
--- a/SourceCode/Bond/Servo/CEquipment.cpp
+++ b/SourceCode/Bond/Servo/CEquipment.cpp
@@ -1,4 +1,4 @@
-#include "stdafx.h"
+锘�#include "stdafx.h"
 #include "CEquipment.h"
 #include "ToolUnits.h"
 #include <regex>
@@ -103,7 +103,7 @@
 	void CEquipment::getProperties(std::vector<std::pair<std::string, std::string>>& container)
 	{
 		container.clear();
-		// 示例:将一些属性添加到容器
+		// 绀轰緥锛氬皢涓�浜涘睘鎬ф坊鍔犲埌瀹瑰櫒
 		container.push_back(std::make_pair("DeviceName", "ServoMotor"));
 		container.push_back(std::make_pair("SerialNumber", "123456789"));
 		container.push_back(std::make_pair("Version", "1.0"));
@@ -147,11 +147,12 @@
 	{
 		if (nSlotNo <= 0 || nSlotNo > 8) return;
 
+		const auto prevState = m_processState[nSlotNo - 1];
 		m_processState[nSlotNo - 1] = state;
-		onProcessStateChanged(nSlotNo, m_processState[nSlotNo - 1]);
+		onProcessStateChanged(nSlotNo, prevState, m_processState[nSlotNo - 1]);
 
 		if (m_listener.onProcessStateChanged != nullptr) {
-			m_listener.onProcessStateChanged(this, nSlotNo, m_processState[nSlotNo - 1]);
+			m_listener.onProcessStateChanged(this, nSlotNo, prevState, m_processState[nSlotNo - 1]);
 		}
 	}
 
@@ -205,6 +206,21 @@
 	std::string& CEquipment::getDescription()
 	{
 		return m_strDescription;
+	}
+
+	void CEquipment::setCurrentRecipe(const std::string& recipe)
+	{
+		Lock();
+		m_currentRecipe = recipe;
+		Unlock();
+	}
+
+	std::string CEquipment::getCurrentRecipe()
+	{
+		Lock();
+		std::string out = m_currentRecipe;
+		Unlock();
+		return out;
 	}
 
 	void CEquipment::setStation(int network, int station)
@@ -315,7 +331,7 @@
 
 	void CEquipment::onTimer(UINT nTimerid)
 	{
-		// 每隔一秒,检查一下ALIVE状态
+		// 姣忛殧涓�绉掞紝妫�鏌ヤ竴涓婣LIVE鐘舵��
 
 		static int tick = 0;
 		tick++;
@@ -379,7 +395,7 @@
 				}
 			}
 
-			// 梳理各玻璃之间的绑定关系
+			// 姊崇悊鍚勭幓鐠冧箣闂寸殑缁戝畾鍏崇郴
 			/*
 			Lock();
 			for (int i = 0; i < SLOT_MAX; i++) {
@@ -391,7 +407,7 @@
 							CGlass* pBudy = (CGlass*)m_slot[j].getContext();
 							if (pBudy != nullptr && strBuddyId.compare(pBudy->getID()) == 0) {
 								pGlass->setBuddy(pBudy);
-								TRACE("绑定关系: %s <- %s\n", pGlass->getID().c_str(), pBudy->getID().c_str());
+								TRACE("缁戝畾鍏崇郴: %s <- %s\n", pGlass->getID().c_str(), pBudy->getID().c_str());
 							}
 						}
 					}
@@ -412,7 +428,7 @@
 		}
 		*/
 
-		// 连接信号解释和保存
+		// 杩炴帴淇″彿瑙i噴鍜屼繚瀛�
 		BOOL bFlag;
 		int index = 0;
 		for (int i = 0; i < 8; i++) {
@@ -442,7 +458,7 @@
 		}
 
 
-		// 其它信号及响应
+		// 鍏跺畠淇″彿鍙婂搷搴�
 		index = 0x540;
 
 
@@ -452,7 +468,7 @@
 			m_alive.flag = bFlag;
 			m_alive.count = 0;
 
-			// 状态
+			// 鐘舵��
 			if (!m_alive.alive) {
 				m_alive.alive = TRUE;
 				if (m_listener.onAlive != nullptr) {
@@ -501,7 +517,7 @@
 		}
 
 
-		// 以下根据信号做流程处理
+		// 浠ヤ笅鏍规嵁淇″彿鍋氭祦绋嬪鐞�
 		for (int i = 0; i < 7; i++) {
 			CHECK_READ_STEP_SIGNAL(STEP_ID_EQMODE_CHANGED + i, pszData, size);
 		}
@@ -510,15 +526,22 @@
 		CHECK_READ_STEP_SIGNAL(STEP_ID_PROCESS_DATA_REPORT, pszData, size);
 
 		// FAC Data report
-		CHECK_READ_STEP_SIGNAL(STEP_ID_FAC_DATA_REPORT, pszData, size);
+		// CHECK_READ_STEP_SIGNAL(STEP_ID_FAC_DATA_REPORT, pszData, size);
+		{
+			SERVO::CStep* pStep = getStep(STEP_ID_FAC_DATA_REPORT);
+			if (pStep != nullptr) {
+					((CReadStep*)pStep)->onReadSignal(TRUE);
+			}
+		}
 
-		// 配方改变
+
+		// 閰嶆柟鏀瑰彉
 		CHECK_READ_STEP_SIGNAL(STEP_ID_CURRENT_RECIPE_CHANGE_REPORT, pszData, size);
 		
-		// 主配方上报
+		// 涓婚厤鏂逛笂鎶�
 		CHECK_READ_STEP_SIGNAL(STEP_ID_MASTER_RECIPE_LIST_REPORT, pszData, size);
 
-		// 配方参数
+		// 閰嶆柟鍙傛暟
 		CHECK_WRITE_STEP_SIGNAL(STEP_ID_RECIPE_PARAMETER_CMD_REPLY, pszData, size);
 		CHECK_READ_STEP_SIGNAL(STEP_ID_RECIPE_PARAMETER_REPORT, pszData, size);
 		
@@ -759,8 +782,8 @@
 			else if (isCimMessageConfirmStep(pStep)) {
 				SERVO::CEqReadIntStep* pEqReadIntStep = (SERVO::CEqReadIntStep*)pStep;
 				int value = pEqReadIntStep->getValue();
-				// 此处将value按高低位拆分为message id和panel no.
-				// 可能还需要上报到cim
+				// 姝ゅ灏唙alue鎸夐珮浣庝綅鎷嗗垎涓簃essage id鍜宲anel no.
+				// 鍙兘杩橀渶瑕佷笂鎶ュ埌cim
 				short msgId, panelNo;
 				msgId = (value & 0xffff0000 >> 16);
 				panelNo = (value & 0xffff);
@@ -775,7 +798,7 @@
 					m_listener.onVcrEventReport(this, pVcrEventReport);
 				}
 
-				// 0426, 先固定返回1(OK)
+				// 0426, 鍏堝浐瀹氳繑鍥�1(OK)
 				pEqVcrEventStep->setReturnCode(1);		
 				return 1;
 			}
@@ -788,12 +811,12 @@
 
 	CPin* CEquipment::addPin(PinType type, char* pszName)
 	{
-		// 不允许名字添加重复的pin
+		// 涓嶅厑璁稿悕瀛楁坊鍔犻噸澶嶇殑pin
 		CPin* pPin = getPin(pszName);
 		if (pPin != nullptr) return nullptr;
 
 
-		// 添加到Pin列表,看是输入pin或输出pin
+		// 娣诲姞鍒癙in鍒楄〃锛岀湅鏄緭鍏in鎴栬緭鍑簆in
 		if (type == PinType::INPUT) {
 			pPin = new CPin(this, type, pszName);
 			m_inputPins.push_back(pPin);
@@ -858,7 +881,7 @@
 		CEquipment* pFromEq = pFromPin->getEquipment();
 		ASSERT(pFromEq);
 
-		LOGD("<CEquipment><%s-%s>收到来自<%s.%s>的Intent<%d,%s,0x%x>",
+		LOGD("<CEquipment><%s-%s>鏀跺埌鏉ヨ嚜<%s.%s>鐨処ntent<%d,%s,0x%x>",
 			this->getName().c_str(),
 			pPin->getName().c_str(),
 			pFromEq->getName().c_str(),
@@ -869,11 +892,11 @@
 
 
 
-		// 以下解释处理数据
+		// 浠ヤ笅瑙i噴澶勭悊鏁版嵁
 		int code = pIntent->getCode();
 
 
-		// 测试
+		// 娴嬭瘯
 		if (code == FLOW_TEST) {
 			AfxMessageBox(pIntent->getMsg());
 		}
@@ -888,7 +911,7 @@
 			return -1;
 		}
 
-		// 找到指定的glass id, 
+		// 鎵惧埌鎸囧畾鐨刧lass id, 
 		Lock();
 		CGlass* pContext = nullptr;
 		for (int i = 0; i < SLOT_MAX; i++) {
@@ -939,7 +962,7 @@
 		CGlass* pBuddy = pGlass->getBuddy();
 		if (pBuddy != nullptr) pBuddy->addPath(m_nID, getSlotUnit(putSlot), putSlot);
 		m_slot[putSlot - 1].setContext(pGlass);
-		pGlass->release();				// tempFetchOut需要调用一次release
+		pGlass->release();				// tempFetchOut闇�瑕佽皟鐢ㄤ竴娆elease
 		Unlock();
 
 		/*
@@ -1167,17 +1190,17 @@
 			return -1;
 		}
 
-		LOGI("<CEquipment-%s>准备设置DispatchingMode<%d>", m_strName.c_str(), (int)mode);
+		LOGI("<CEquipment-%s>鍑嗗璁剧疆DispatchingMode<%d>", m_strName.c_str(), (int)mode);
 		if (onWritedBlock != nullptr) {
 			pStep->writeShort((short)mode, onWritedBlock);
 		}
 		else {
 			pStep->writeShort((short)mode, [&, mode](int code) -> int {
 				if (code == WOK) {
-					LOGI("<CEquipment-%s>设置DispatchingMode成功.", m_strName.c_str());
+					LOGI("<CEquipment-%s>璁剧疆DispatchingMode鎴愬姛.", m_strName.c_str());
 				}
 				else {
-					LOGE("<CEquipment-%s>设置DispatchingMode失败,code:%d", m_strName.c_str(), code);
+					LOGE("<CEquipment-%s>璁剧疆DispatchingMode澶辫触锛宑ode:%d", m_strName.c_str(), code);
 				}
 
 				return 0;
@@ -1195,19 +1218,19 @@
 		}
 
 		unsigned short operationMode = (unsigned short)((unsigned short)mode + getIndexerOperationModeBaseValue());
-		LOGI("<CEquipment-%s>准备设置indexerOperationMode<%d>", m_strName.c_str(), (int)mode);
+		LOGI("<CEquipment-%s>鍑嗗璁剧疆indexerOperationMode<%d>", m_strName.c_str(), (int)mode);
 		pStep->writeShort(operationMode, [&, pStep, mode, onWritedRetBlock](int code) -> int {
 			int retCode = 0;
 			if (code == WOK) {
-				LOGI("<CEquipment-%s>设置indexerOperationMode成功.", m_strName.c_str());
+				LOGI("<CEquipment-%s>璁剧疆indexerOperationMode鎴愬姛.", m_strName.c_str());
 				const char* pszRetData = nullptr;
 				pStep->getReturnData(pszRetData);
 				ASSERT(pszRetData);
 				retCode = (unsigned int)CToolUnits::toInt16(pszRetData);
-				LOGI("<CEquipment-%s>返回值: %d", m_strName.c_str(), retCode);
+				LOGI("<CEquipment-%s>杩斿洖鍊�: %d", m_strName.c_str(), retCode);
 			}
 			else {
-				LOGE("<CEquipment-%s>设置indexerOperationMode失败,code:%d", m_strName.c_str(), code);
+				LOGE("<CEquipment-%s>璁剧疆indexerOperationMode澶辫触锛宑ode:%d", m_strName.c_str(), code);
 			}
 
 			if (onWritedRetBlock != nullptr) {
@@ -1227,18 +1250,18 @@
 			return -1;
 		}
 
-		LOGI("<CEquipment-%s>正在请求单元<%d>主配方列表", m_strName.c_str(), unitNo);
+		LOGI("<CEquipment-%s>姝e湪璇锋眰鍗曞厓<%d>涓婚厤鏂瑰垪琛�", m_strName.c_str(), unitNo);
 		m_recipesManager.setOnSyncingStateChanged(block);
 		if (m_recipesManager.syncing() != 0) {
 			return -2;
 		}
 		pStep->writeShort(unitNo, [&, unitNo](int code) -> int {
 			if (code == WOK) {
-				LOGI("<CEquipment-%s>请求单元<%d>主配方列表成功,正在等待数据.", m_strName.c_str(), unitNo);
+				LOGI("<CEquipment-%s>璇锋眰鍗曞厓<%d>涓婚厤鏂瑰垪琛ㄦ垚鍔燂紝姝e湪绛夊緟鏁版嵁.", m_strName.c_str(), unitNo);
 			}
 			else {
 				m_recipesManager.syncFailed();
-				LOGE("<CEquipment-%s>请求单元<%d>主配方列表失败,code:%d", m_strName.c_str(), unitNo, code);
+				LOGE("<CEquipment-%s>璇锋眰鍗曞厓<%d>涓婚厤鏂瑰垪琛ㄥけ璐ワ紝code:%d", m_strName.c_str(), unitNo, code);
 			}
 
 			return 0;
@@ -1248,7 +1271,7 @@
 
 	int CEquipment::recipeParameterRequest(short masterRecipeId, short localRecipeId, short unitNo, ONSYNCINGSTATECHANGED block)
 	{
-		LOGI("<CEquipment-%s>正在请求单元<%d>主配参数列表", m_strName.c_str(), unitNo);
+		LOGI("<CEquipment-%s>姝e湪璇锋眰鍗曞厓<%d>涓婚厤鍙傛暟鍒楄〃", m_strName.c_str(), unitNo);
 		m_recipesManager.setOnSyncingStateChanged(block);
 		if (m_recipesManager.syncing() != 0) {
 			return -2;
@@ -1269,11 +1292,11 @@
 
 		pStep->writeDataEx(szBuffer, 14 * 2, [&, unitNo](int code) -> int {
 			if (code == WOK) {
-				LOGI("<CEquipment-%s>请求单元<%d>主配方参数列表成功,正在等待数据.", m_strName.c_str(), unitNo);
+				LOGI("<CEquipment-%s>璇锋眰鍗曞厓<%d>涓婚厤鏂瑰弬鏁板垪琛ㄦ垚鍔燂紝姝e湪绛夊緟鏁版嵁.", m_strName.c_str(), unitNo);
 			}
 			else {
 				m_recipesManager.syncFailed();
-				LOGE("<CEquipment-%s>请求单元<%d>主配方参数列表失败,code:%d", m_strName.c_str(), unitNo, code);
+				LOGE("<CEquipment-%s>璇锋眰鍗曞厓<%d>涓婚厤鏂瑰弬鏁板垪琛ㄥけ璐ワ紝code:%d", m_strName.c_str(), unitNo, code);
 			}
 
 			return 0;
@@ -1577,7 +1600,7 @@
 		int nRet = processData.unserialize(&pszData[0], (int)size);
 		if (nRet < 0) return nRet;
 
-		// 缓存Attribute,用于调试时显示信息
+		// 缂撳瓨Attribute锛岀敤浜庤皟璇曟椂鏄剧ず淇℃伅
 		unsigned int weight = 201;
 		CAttributeVector& attrubutes = pStep->attributeVector();
 		processData.getAttributeVector(attrubutes, weight);
@@ -1585,11 +1608,11 @@
 
 
 
-		// 找到玻璃,关联数据
+		// 鎵惧埌鐜荤拑锛屽叧鑱旀暟鎹�
 		CGlass* pGlass = this->getGlassWithCassette(processData.getCassetteSequenceNo(),
 			processData.getJobSequenceNo());
 		if (pGlass == nullptr) {
-			LOGE("<CEquipment-%s>找不到对应Glass, 关联工艺参数失败。CassetteSequenceNo:%d/%d",
+			LOGE("<CEquipment-%s>鎵句笉鍒板搴擥lass, 鍏宠仈宸ヨ壓鍙傛暟澶辫触銆侰assetteSequenceNo:%d/%d",
 				this->getName().c_str(),
 				processData.getCassetteSequenceNo(),
 				processData.getJobSequenceNo());
@@ -1600,15 +1623,21 @@
 		std::vector<CParam> tempParams;
 		this->parsingProcessData((const char*)rawData.data(), rawData.size(), tempParams);
 		int n = processData.getTotalParameter();
-		std::vector<CParam> params(tempParams.begin(), tempParams.begin() + min(n, (int)tempParams.size()));
+		std::vector<CParam> params(tempParams.begin(), tempParams.begin() + (std::min)(n, (int)tempParams.size()));
 		pGlass->addParams(params);
+		if (m_listener.onProcessDataReport != nullptr) {
+			m_listener.onProcessDataReport(this, params);
+		}
 		
-		// 关联的Glass也要更新
+		// 鍏宠仈鐨凣lass涔熻鏇存柊
 		CGlass* pBuddy = pGlass->getBuddy();
 		LOGI("<Equipment-%s>decodeProcessDataReport pBuddy=%x %s", getName().c_str(), pBuddy, pGlass->getID().c_str());
 		if (pBuddy != nullptr) {
 			LOGI("<Equipment-%s>decodeProcessDataReport addParams pBuddy=%x %s", getName().c_str(), pBuddy, pGlass->getID().c_str());
 			pBuddy->addParams(params);
+			if (m_listener.onProcessDataReport != nullptr) {
+				m_listener.onProcessDataReport(this, params);
+			}
 		}
 
 		return nRet;
@@ -1620,7 +1649,7 @@
 		int nRet = jobDataS.unserialize(&pszData[0], (int)size);
 		if (nRet < 0) return nRet;
 
-		// 缓存Attribute,用于调试时显示信息
+		// 缂撳瓨Attribute锛岀敤浜庤皟璇曟椂鏄剧ず淇℃伅
 		unsigned int weight = 201;
 		CAttributeVector& attrubutes = pStep->attributeVector();
 		jobDataS.getAttributeVector(attrubutes, weight);
@@ -1634,11 +1663,15 @@
 		LOGI("<CEquipment-%s>onReceivedJob.", m_strName.c_str());
 
 
-		// 可以在此更新JobDataS数据了
+		// 鍙互鍦ㄦ鏇存柊JobDataS鏁版嵁浜�
 		int nRet = ((CArm*)m_pArm)->glassUpdateJobDataS(pJobDataS);
 		if (nRet < 0) {
-			LOGE("<CEquipment-%s>onReceivedJob,更新JobDataS失败,glassUpdateJobDataS返回%d",
+			LOGE("<CEquipment-%s>onReceivedJob,鏇存柊JobDataS澶辫触锛実lassUpdateJobDataS杩斿洖%d",
 				m_strName.c_str(), nRet);
+		}
+
+		if (m_listener.onReceivedJob != nullptr) {
+			m_listener.onReceivedJob(this, port, pJobDataS);
 		}
 
 		return nRet;
@@ -1650,7 +1683,7 @@
 		int nRet = jobDataS.unserialize(&pszData[0], (int)size);
 		if (nRet < 0) return nRet;
 
-		// 缓存Attribute,用于调试时显示信息
+		// 缂撳瓨Attribute锛岀敤浜庤皟璇曟椂鏄剧ず淇℃伅
 		unsigned int weight = 201;
 		CAttributeVector& attrubutes = pStep->attributeVector();
 		jobDataS.getAttributeVector(attrubutes, weight);
@@ -1662,6 +1695,10 @@
 	int CEquipment::onSentOutJob(int port, CJobDataS* pJobDataS)
 	{
 		LOGI("<CEquipment-%s>onSentOutJob.", m_strName.c_str());
+
+		if (m_listener.onSentOutJob != nullptr) {
+			m_listener.onSentOutJob(this, port, pJobDataS);
+		}
 
 		return 0;
 	}
@@ -1685,7 +1722,7 @@
 		index += sizeof(short);
 
 
-		// 缓存Attribute,用于调试时显示信息
+		// 缂撳瓨Attribute锛岀敤浜庤皟璇曟椂鏄剧ず淇℃伅
 		unsigned int weight = 201;
 		pStep->addAttribute(new CAttribute("UnitOrPort",
 			std::to_string(unitOrPort).c_str(), "", weight++));
@@ -1729,7 +1766,7 @@
 			return fetchedOutJob(port, pJobDataB);
 		}
 
-		// 数据异常,处理或显示
+		// 鏁版嵁寮傚父锛屽鐞嗘垨鏄剧ず
 		LOGI("<CEquipment-%s>onFetchedOutJob Error.ort:%d|GlassId:%s",
 			m_strName.c_str(), port, pJobDataB->getGlassId().c_str());
 		return -1;
@@ -1754,7 +1791,7 @@
 		index += sizeof(short);
 
 
-		// 缓存Attribute,用于调试时显示信息
+		// 缂撳瓨Attribute锛岀敤浜庤皟璇曟椂鏄剧ず淇℃伅
 		unsigned int weight = 201;
 		pStep->addAttribute(new CAttribute("UnitOrPort",
 			std::to_string(unitOrPort).c_str(), "", weight++));
@@ -1786,7 +1823,7 @@
 			vcrEventReport.getGlassId().c_str());
 
 
-		// 更新Glass的ID
+		// 鏇存柊Glass鐨処D
 		CGlass* pGlass = getGlassWithCassette(vcrEventReport.getCassetteSequenceNo(),
 			vcrEventReport.getJobSequenceNo());
 		if (pGlass != nullptr) {
@@ -1794,13 +1831,13 @@
 		}
 		
 
-		// 缓存Attribute,用于调试时显示信息
+		// 缂撳瓨Attribute锛岀敤浜庤皟璇曟椂鏄剧ず淇℃伅
 		unsigned int weight = 201;
 		CAttributeVector& attrubutes = pStep->attributeVector();
 		vcrEventReport.getAttributeVector(attrubutes, weight);
 
 
-		// 0426, 先固定返回1(OK)
+		// 0426, 鍏堝浐瀹氳繑鍥�1(OK)
 		((CReadStep*)pStep)->setReturnCode((short)VCR_Reply_Code::OK);
 
 
@@ -1822,7 +1859,7 @@
 		index += 256 * 2;
 
 
-		// 缓存Attribute,用于调试时显示信息
+		// 缂撳瓨Attribute锛岀敤浜庤皟璇曟椂鏄剧ず淇℃伅
 		unsigned int weight = 201;
 		pStep->addAttribute(new CAttribute("CassetteNo",
 			std::to_string(cassetteNo).c_str(), "", weight++));
@@ -1834,10 +1871,10 @@
 			strPanelGradeData.c_str(), "", weight++));
 
 
-		// 更新检测结果
+		// 鏇存柊妫�娴嬬粨鏋�
 		CGlass* pGlass = getGlassWithCassette(cassetteNo, jobSequenceNo);
 		if (pGlass == nullptr) {
-			LOGE("<CEquipment-%s>更新Panel Data失败,找不到对应的Glass.cassetteNo=%d, jobSequenceNo=%d",
+			LOGE("<CEquipment-%s>鏇存柊Panel Data澶辫触锛屾壘涓嶅埌瀵瑰簲鐨凣lass.cassetteNo=%d, jobSequenceNo=%d",
 				getName().c_str(), cassetteNo, jobSequenceNo);
 			return -1;
 		}
@@ -1856,7 +1893,9 @@
 		CSVData svData;
 		int nRet = svData.unserialize(&pszData[0], (int)size);
 		if (nRet < 0) return nRet;
+		Lock();
 		m_svDatas.push_back(svData);
+		Unlock();
 
 		if (m_listener.onSVDataReport != nullptr) {
 			m_listener.onSVDataReport(this, &svData);
@@ -1878,7 +1917,7 @@
 
 
 
-		// 缓存Attribute,用于调试时显示信息
+		// 缂撳瓨Attribute锛岀敤浜庤皟璇曟椂鏄剧ず淇℃伅
 		unsigned int weight = 201;
 		pStep->addAttribute(new CAttribute("CassetteSequenceNo",
 			(std::to_string(cassetteSequenceNo)).c_str(), "", weight++));
@@ -1934,7 +1973,7 @@
 
 		CGlass* pGlass = getGlassFromSlot(slotNo);
 		if (pGlass == nullptr) {
-			LOGE("<CEquipment-%s>decodeJobProcessStartReport, 找不到对应glass", getName().c_str());
+			LOGE("<CEquipment-%s>decodeJobProcessStartReport, 鎵句笉鍒板搴攇lass", getName().c_str());
 		}
 		if (slotNo <= 0 || slotNo > 8) return -1;
 
@@ -1946,7 +1985,7 @@
 		}
 
 
-		// 缓存Attribute,用于调试时显示信息
+		// 缂撳瓨Attribute锛岀敤浜庤皟璇曟椂鏄剧ず淇℃伅
 		unsigned int weight = 201;
 		pStep->addAttribute(new CAttribute("CassetteNo",
 			std::to_string(cassetteNo).c_str(), "", weight++));
@@ -2016,7 +2055,7 @@
 		}
 	
 		if (pGlass == nullptr) {
-			LOGE("<CEquipment-%s>decodeJobProcessEndReport, 找不到对应glass", getName().c_str());
+			LOGE("<CEquipment-%s>decodeJobProcessEndReport, 鎵句笉鍒板搴攇lass", getName().c_str());
 		}
 		else {
 			CJobDataS* pJs = pGlass->getJobDataS();
@@ -2025,7 +2064,7 @@
 				pGlass->processEnd(m_nID, getSlotUnit(slotNo));
 			}
 			else {
-				LOGE("<CEquipment-%s>decodeJobProcessEndReport, jobSequenceNo或jobSequenceNo不匹配",
+				LOGE("<CEquipment-%s>decodeJobProcessEndReport, jobSequenceNo鎴杍obSequenceNo涓嶅尮閰�",
 					getName().c_str());
 			}
 		}
@@ -2033,7 +2072,7 @@
 
 
 
-		// 缓存Attribute,用于调试时显示信息
+		// 缂撳瓨Attribute锛岀敤浜庤皟璇曟椂鏄剧ず淇℃伅
 		unsigned int weight = 201;
 		pStep->addAttribute(new CAttribute("CassetteNo",
 			std::to_string(cassetteNo).c_str(), "", weight++));
@@ -2057,10 +2096,10 @@
 		LOGI("<CEquipment-%s>onPreStoredJob:port:%d|GlassId:%s",
 			m_strName.c_str(), port, pJobDataB->getGlassId().c_str());
 
-		// 当前要存片,之前肯定有拔片,因此片子在Arm那里
+		// 褰撳墠瑕佸瓨鐗囷紝涔嬪墠鑲畾鏈夋嫈鐗囷紝鍥犳鐗囧瓙鍦ˋrm閭i噷
 		CGlass* pGlass = ((CArm*)m_pArm)->getGlassFromSlot(1);
 		if (pGlass == nullptr) {
-			LOGE("<CFliper-%s>onPreStoredJob,缓存中没有找到对应的Glass(CassetteSequenceNo:%d, JobSequenceNo:%d),请检查数据,注意风险。", m_strName.c_str(),
+			LOGE("<CFliper-%s>onPreStoredJob,缂撳瓨涓病鏈夋壘鍒板搴旂殑Glass(CassetteSequenceNo:%d, JobSequenceNo:%d)锛岃妫�鏌ユ暟鎹紝娉ㄦ剰椋庨櫓銆�", m_strName.c_str(),
 				pJobDataB->getCassetteSequenceNo(), pJobDataB->getJobSequenceNo());
 			return FALSE;
 		}
@@ -2068,19 +2107,19 @@
 		CJobDataS* pJobDataS = pGlass->getJobDataS();
 		ASSERT(pJobDataS);
 		if (!compareJobData(pJobDataB, pJobDataS)) {
-			LOGE("<CEquipemnt-%s>onPreStoredJob,JobData数据不匹配(JobDataB(%d, %d),JobDataS(%d, %d)), 注意排查风险!", m_strName.c_str(),
+			LOGE("<CEquipemnt-%s>onPreStoredJob,JobData鏁版嵁涓嶅尮閰�(JobDataB(%d, %d),JobDataS(%d, %d)), 娉ㄦ剰鎺掓煡椋庨櫓!", m_strName.c_str(),
 				pJobDataB->getCassetteSequenceNo(), pJobDataB->getJobSequenceNo(),
 				pJobDataS->getCassetteSequenceNo(), pJobDataS->getJobSequenceNo());
 			return FALSE;
 		}
 
-		// 如果没有可用位置,报错
+		// 濡傛灉娌℃湁鍙敤浣嶇疆锛屾姤閿�
 		Lock();
 		CSlot* pSlot = getSlot(putSlot - 1);
 		ASSERT(pSlot);
 		if (pSlot->getContext() != nullptr) {
 			Unlock();
-			LOGE("<CEquipemnt-%s>onPreStoredJob,指定slot(port:%d)有料,请注意风险!", m_strName.c_str(), port);
+			LOGE("<CEquipemnt-%s>onPreStoredJob,鎸囧畾slot(port:%d)鏈夋枡锛岃娉ㄦ剰椋庨櫓锛�", m_strName.c_str(), port);
 			return FALSE;
 		}
 		Unlock();
@@ -2110,7 +2149,7 @@
 			return storedJob(port, pJobDataB, putSlot);
 		}
 
-		// 数据异常,处理或显示
+		// 鏁版嵁寮傚父锛屽鐞嗘垨鏄剧ず
 		LOGI("<CEquipment-%s>onStoredJob Error.port:%d|GlassId:%s",
 			m_strName.c_str(), port, pJobDataB->getGlassId().c_str());
 		return -1;
@@ -2124,8 +2163,8 @@
 	}
 
 	/*
-	 * 当从CC-Link检测到设备Send Able为On时调用此函数
-	 * 可能会多次重复调用(根据扫描频率), 注意防呆
+	 * 褰撲粠CC-Link妫�娴嬪埌璁惧Send Able涓篛n鏃惰皟鐢ㄦ鍑芥暟
+	 * 鍙兘浼氬娆¢噸澶嶈皟鐢�(鏍规嵁鎵弿棰戠巼), 娉ㄦ剰闃插憜
 	 */
 	int CEquipment::onSendAble(int port)
 	{
@@ -2141,7 +2180,7 @@
 		return 0;
 	}
 
-	int CEquipment::onProcessStateChanged(int nSlotNo, PROCESS_STATE state)
+	int CEquipment::onProcessStateChanged(int nSlotNo, PROCESS_STATE prevState, PROCESS_STATE state)
 	{
 		return 0;
 	}
@@ -2243,6 +2282,7 @@
 			});
 		pStep->setName(STEP_EQ_FAC_DATA_REPORT);
 		pStep->setProp("Port", (void*)(__int64)port);
+		pStep->setReadContinue(TRUE);
 		pStep->setWriteSignalDev(writeSignalDev);
 		if (addStep(STEP_ID_FAC_DATA_REPORT, pStep) != 0) {
 			delete pStep;
@@ -2253,4 +2293,4 @@
 	{
 		return m_svDatas;
 	}
-}
\ No newline at end of file
+}
diff --git a/SourceCode/Bond/Servo/CEquipment.h b/SourceCode/Bond/Servo/CEquipment.h
index 36a05c9..0ed9452 100644
--- a/SourceCode/Bond/Servo/CEquipment.h
+++ b/SourceCode/Bond/Servo/CEquipment.h
@@ -1,4 +1,4 @@
-#pragma once
+锘�#pragma once
 #include "Log.h"
 #include "ServoCommo.h"
 #include "CCLinkIEControl.h"
@@ -55,9 +55,12 @@
 	typedef std::function<void(void* pEiuipment, void* pReport)> ONVCREVENTREPORT;
 	typedef std::function<BOOL(void* pEiuipment, int port, CJobDataB* pJobDataB)> ONPREFETCHEDOUTJOB;
 	typedef std::function<BOOL(void* pEiuipment, int port, CJobDataB* pJobDataB, short& putSlot)> ONPRESTOREDJOB;
-	typedef std::function<void(void* pEiuipment, int nSlotNo, PROCESS_STATE state)> ONPROCESSSTATE;
+	typedef std::function<void(void* pEiuipment, int nSlotNo, PROCESS_STATE prevState, PROCESS_STATE state)> ONPROCESSSTATE;
 	typedef std::function<void(void* pEiuipment, short scanMap, short downMap)> ONMAPMISMATCH;
 	typedef std::function<void(void* pEiuipment, short status, __int64 data)> ONPORTSTATUSCHANGED;
+	typedef std::function<void(void* pEiuipment, const std::vector<CParam>& params)> ONPROCESSDATAREPORT;
+	typedef std::function<void(void* pEiuipment, int port, CJobDataS* pJobDataS)> ONRECEIVEDJOB;
+	typedef std::function<void(void* pEiuipment, int port, CJobDataS* pJobDataS)> ONSENTOUTJOB;
 	
 	typedef struct _EquipmentListener
 	{
@@ -73,6 +76,9 @@
 		ONPORTSTATUSCHANGED	onPortStatusChanged;
 		ONVCREVENTREPORT	onSVDataReport;
 		ONVCREVENTREPORT	onPanelDataReport;
+		ONPROCESSDATAREPORT	onProcessDataReport;
+		ONRECEIVEDJOB		onReceivedJob;
+		ONSENTOUTJOB		onSentOutJob;
 	} EquipmentListener;
 
 
@@ -99,6 +105,8 @@
 		std::string& getName();
 		void setDescription(const char* pszDescription);
 		std::string& getDescription();
+		void setCurrentRecipe(const std::string& recipe);
+		std::string getCurrentRecipe();
 		void setStation(int network, int station);
 		const StationIdentifier& getStation();
 		virtual void getAttributeVector(CAttributeVector& attrubutes);
@@ -140,7 +148,7 @@
 		virtual int onProcessData(CProcessData* pProcessData);
 		virtual int onSendAble(int port);
 		virtual int onReceiveAble(int port);
-		virtual int onProcessStateChanged(int nSlotNo, PROCESS_STATE state);
+		virtual int onProcessStateChanged(int nSlotNo, PROCESS_STATE prevState, PROCESS_STATE state);
 		virtual int getIndexerOperationModeBaseValue();
 		virtual bool isSlotProcessed(int slot) { return true; };
 		bool isAlarmStep(SERVO::CStep* pStep);
@@ -163,47 +171,47 @@
 		void printDebugString001();
 		std::vector<SERVO::CSVData>& getSVDatas();
 
-		// 请求主配方列表
+		// 璇锋眰涓婚厤鏂瑰垪琛�
 		// unitNo: 0:local; Others:unit No
 		int masterRecipeListRequest(short unitNo, ONSYNCINGSTATECHANGED block);
 
-		// 请求配方参数
-		// masterRecipeId: 主配方id
-		// localRecipeId: 本地配方id
+		// 璇锋眰閰嶆柟鍙傛暟
+		// masterRecipeId: 涓婚厤鏂筰d
+		// localRecipeId: 鏈湴閰嶆柟id
 		// unitNo: 0:local; Others:unit No
 		int recipeParameterRequest(short masterRecipeId, short localRecipeId, short unitNo, ONSYNCINGSTATECHANGED block);
 
-		// 解析配方参数列表
+		// 瑙f瀽閰嶆柟鍙傛暟鍒楄〃
 		virtual int parsingParams(const char* pszData, size_t size, std::vector<CParam>& params) { return 0;  };
 		virtual int parsingParams(const char* pszData, size_t size, std::string& strOut);
 		virtual int parsingProcessData(const char* pszData, size_t size, std::vector<CParam>& params) { return 0; };
 		virtual int parsingSVData(const char* pszData, size_t size, std::vector<CParam>& params) { return 0; };
 
-		// 获取指定的Slot
+		// 鑾峰彇鎸囧畾鐨凷lot
 		CSlot* getSlot(int index);
 		CSlot* getSlotWithNo(int slotNo);
 
-		// 获取一个可用的槽位
+		// 鑾峰彇涓�涓彲鐢ㄧ殑妲戒綅
 		CSlot* getAvailableSlot();
 
-		// 获取一个指定物料类型(G1,G2,G1&G2)的空槽位
+		// 鑾峰彇涓�涓寚瀹氱墿鏂欑被鍨�(G1,G2,G1&G2)鐨勭┖妲戒綅
 		CSlot* getAvailableSlotForGlass(MaterialsType type);
 		CSlot* getAvailableSlotForGlassExcludeSignal(MaterialsType type);
 		CSlot* isSlotAvailable(unsigned int slot);
 
-		// 在指定的槽列表中,获取一个指定物料类型(G1,G2,G1&G2)的空槽位
+		// 鍦ㄦ寚瀹氱殑妲藉垪琛ㄤ腑锛岃幏鍙栦竴涓寚瀹氱墿鏂欑被鍨�(G1,G2,G1&G2)鐨勭┖妲戒綅
 		CSlot* getAvailableSlotForGlass2(MaterialsType type, const std::vector<int>& candidates);
 
-		// 获取一个指定物料类型(G1,G2,G1&G2)的非空槽位
+		// 鑾峰彇涓�涓寚瀹氱墿鏂欑被鍨�(G1,G2,G1&G2)鐨勯潪绌烘Ы浣�
 		CSlot* getNonEmptySlot(MaterialsType type);
 
-		// 获取一个指定物料类型(G1,G2,G1&G2)的且已经加工处理的槽位
+		// 鑾峰彇涓�涓寚瀹氱墿鏂欑被鍨�(G1,G2,G1&G2)鐨勪笖宸茬粡鍔犲伐澶勭悊鐨勬Ы浣�
 		CSlot* getProcessedSlot(MaterialsType putSlotType, BOOL bJobMode = FALSE);
 		CSlot* getProcessedSlot2(MaterialsType putSlotType, const std::vector<int>& candidates);
 		CSlot* getInspFailSlot();
 		CSlot* getProcessedSlotCt(unsigned int slot);
 
-		// 获取玻璃物料
+		// 鑾峰彇鐜荤拑鐗╂枡
 		CGlass* getGlassFromSlot(int slotNo);
 		CGlass* getGlassWithCassette(int cassetteSequenceNo, int jobSequenceNo);
 		CGlass* getAnyGlass();
@@ -211,26 +219,27 @@
 		int getAllGlass(std::vector<CGlass*>& glasses);
 		CJobDataS* getJobDataSWithCassette(int cassetteSequenceNo, int jobSequenceNo);
 
-		// 验证玻璃和槽是否匹配
+		// 楠岃瘉鐜荤拑鍜屾Ы鏄惁鍖归厤
 		BOOL ValidateGlassSlotMatch();
 
-		// 是否有玻璃
+		// 鏄惁鏈夌幓鐠�
 		BOOL hasGlass();
 		BOOL slotHasGlass(int slotIndex = 0);
 
-		// 指定槽位是否可以放置玻璃
+		// 鎸囧畾妲戒綅鏄惁鍙互鏀剧疆鐜荤拑
 		BOOL canPlaceGlassInSlot(const short slotIndex);
 
-		// 手动移除物料
+		// 鎵嬪姩绉婚櫎鐗╂枡
 		int removeGlass(int slotNo);
 
-		// 字符串检测结果转换
+		// 瀛楃涓叉娴嬬粨鏋滆浆鎹�
 		InspResult judgeStringToInspResult(std::string& strJudge);
 
+		// for test
+		void fireSetProcessState(int nSlotNo, PROCESS_STATE state) { return setProcessState(nSlotNo, state); }
 
 
-
-	// 以下为从CC-Link读取到的Bit标志位检测函数
+	// 浠ヤ笅涓轰粠CC-Link璇诲彇鍒扮殑Bit鏍囧織浣嶆娴嬪嚱鏁�
 	public:
 		BOOL isAlive();
 		BOOL isCimOn();
@@ -242,7 +251,7 @@
 		BOOL isLinkSignalUpstreamOn(unsigned int path, unsigned int signal);
 		BOOL isLinkSignalDownstreamOn(unsigned int path, unsigned int signal);
 
-		// 只在模拟测试时使用的函数,用于模拟信号
+		// 鍙湪妯℃嫙娴嬭瘯鏃朵娇鐢ㄧ殑鍑芥暟锛岀敤浜庢ā鎷熶俊鍙�
 		void setLinkSignalUpstream(unsigned int path, unsigned int signal, BOOL bOn);
 		void setLinkSignalUpstreamBlock(unsigned int path, BOOL* pSignal);
 		void setLinkSignalDownstream(unsigned int path, unsigned int signal, BOOL bOn);
@@ -271,7 +280,7 @@
 		float toFloat(const char* pszAddr);
 
 	protected:
-		// 部分优化/简化代码、暂实现部分,到时平铺开
+		// 閮ㄥ垎浼樺寲/绠�鍖栦唬鐮併�佹殏瀹炵幇閮ㄥ垎锛屽埌鏃跺钩閾哄紑
 		void addFacDataReportStep(int dataDev, int writeSignalDev, int port);
 
 
@@ -281,6 +290,7 @@
 		int m_nID;
 		std::string m_strName;
 		std::string m_strDescription;
+		std::string m_currentRecipe;
 		CRITICAL_SECTION m_criticalSection;
 		StationIdentifier m_station;
 		MemoryBlock m_blockReadBit;
@@ -289,7 +299,7 @@
 		std::vector<CPin*> m_outputPins;
 
 
-		// 以下为从CC-Link读取到的Bit标志位
+		// 浠ヤ笅涓轰粠CC-Link璇诲彇鍒扮殑Bit鏍囧織浣�
 	protected:
 		ALIVE m_alive;
 		BOOL m_bCimState;			// ON/OFF
diff --git a/SourceCode/Bond/Servo/CEventEditDlg.cpp b/SourceCode/Bond/Servo/CEventEditDlg.cpp
new file mode 100644
index 0000000..1148a59
--- /dev/null
+++ b/SourceCode/Bond/Servo/CEventEditDlg.cpp
@@ -0,0 +1,94 @@
+#include "stdafx.h"
+#include "CEventEditDlg.h"
+#include "Servo.h"
+#include "resource.h"
+#include <algorithm>
+
+IMPLEMENT_DYNAMIC(CEventEditDlg, CDialogEx)
+
+CEventEditDlg::CEventEditDlg(const CString& title, int eventId, const CString& name, const CString& desc, const std::vector<unsigned int>& rptIds, CWnd* pParent)
+	: CDialogEx(IDD_DIALOG_EVENT_EDIT, pParent)
+	, m_strTitle(title)
+	, m_eventId(eventId)
+	, m_strName(name)
+	, m_strDesc(desc)
+	, m_rptIds(rptIds)
+{
+}
+
+CEventEditDlg::~CEventEditDlg()
+{
+}
+
+void CEventEditDlg::DoDataExchange(CDataExchange* pDX)
+{
+	CDialogEx::DoDataExchange(pDX);
+	DDX_Control(pDX, IDC_EDIT_EVT_ID, m_editId);
+	DDX_Control(pDX, IDC_EDIT_EVT_NAME, m_editName);
+	DDX_Control(pDX, IDC_EDIT_EVT_DESC, m_editDesc);
+	DDX_Control(pDX, IDC_LIST_EVT_RPTS, m_listRpt);
+}
+
+BEGIN_MESSAGE_MAP(CEventEditDlg, CDialogEx)
+END_MESSAGE_MAP()
+
+BOOL CEventEditDlg::OnInitDialog()
+{
+	CDialogEx::OnInitDialog();
+	SetWindowText(m_strTitle);
+
+	CString strId;
+	strId.Format(_T("%d"), m_eventId);
+	m_editId.SetWindowText(strId);
+	m_editId.SetReadOnly(TRUE);
+	m_editName.SetWindowText(m_strName);
+	m_editDesc.SetWindowText(m_strDesc);
+
+	m_listRpt.SetExtendedStyle(m_listRpt.GetExtendedStyle() | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_CHECKBOXES);
+	m_listRpt.InsertColumn(0, _T("RPT ID"), LVCFMT_LEFT, 120);
+	m_listRpt.InsertColumn(1, _T("VIDs"), LVCFMT_LEFT, 240);
+
+	auto& reports = theApp.m_model.m_hsmsPassive.getReports();
+	for (int i = 0; i < (int)reports.size(); ++i) {
+		auto rpt = reports[i];
+		if (rpt == nullptr) continue;
+		int idx = m_listRpt.InsertItem(m_listRpt.GetItemCount(), std::to_string(rpt->getReportId()).c_str());
+		m_listRpt.SetItemText(idx, 1, rpt->getVariablesIdsText().c_str());
+		m_listRpt.SetItemData(idx, (DWORD_PTR)rpt->getReportId());
+		if (std::find(m_rptIds.begin(), m_rptIds.end(), rpt->getReportId()) != m_rptIds.end()) {
+			m_listRpt.SetCheck(idx, TRUE);
+		}
+	}
+
+	return TRUE;
+}
+
+void CEventEditDlg::OnOK()
+{
+	CString name, desc;
+	m_editName.GetWindowText(name);
+	m_editDesc.GetWindowText(desc);
+	name.Trim();
+	desc.Trim();
+	if (name.IsEmpty()) {
+		AfxMessageBox(_T("鍚嶇О涓嶈兘涓虹┖"));
+		return;
+	}
+
+	std::vector<unsigned int> selected;
+	int count = m_listRpt.GetItemCount();
+	for (int i = 0; i < count; ++i) {
+		if (m_listRpt.GetCheck(i)) {
+			selected.push_back((unsigned int)m_listRpt.GetItemData(i));
+		}
+	}
+	if (selected.empty()) {
+		AfxMessageBox(_T("鑷冲皯閫夋嫨涓�涓猂eport"));
+		return;
+	}
+
+	m_strName = name;
+	m_strDesc = desc;
+	m_rptIds.swap(selected);
+	CDialogEx::OnOK();
+}
diff --git a/SourceCode/Bond/Servo/CEventEditDlg.h b/SourceCode/Bond/Servo/CEventEditDlg.h
new file mode 100644
index 0000000..a2a1b96
--- /dev/null
+++ b/SourceCode/Bond/Servo/CEventEditDlg.h
@@ -0,0 +1,37 @@
+锘�#pragma once
+#include "afxdialogex.h"
+#include <vector>
+
+// 浜嬩欢缂栬緫瀵硅瘽妗嗭紙鏂板/缂栬緫鍏辩敤锛屽嬀閫塕eport锛�
+class CEventEditDlg : public CDialogEx
+{
+	DECLARE_DYNAMIC(CEventEditDlg)
+
+public:
+	CEventEditDlg(const CString& title, int eventId, const CString& name, const CString& desc, const std::vector<unsigned int>& rptIds, CWnd* pParent = nullptr);
+	virtual ~CEventEditDlg();
+
+	int GetEventId() const { return m_eventId; }
+	CString GetNameText() const { return m_strName; }
+	CString GetDescText() const { return m_strDesc; }
+	const std::vector<unsigned int>& GetSelectedRptIds() const { return m_rptIds; }
+
+protected:
+	virtual BOOL OnInitDialog() override;
+	virtual void DoDataExchange(CDataExchange* pDX) override;
+	afx_msg void OnOK();
+
+	DECLARE_MESSAGE_MAP()
+
+private:
+	CString m_strTitle;
+	int m_eventId;
+	CString m_strName;
+	CString m_strDesc;
+	std::vector<unsigned int> m_rptIds;
+
+	CEdit m_editId;
+	CEdit m_editName;
+	CEdit m_editDesc;
+	CListCtrl m_listRpt;
+};
diff --git a/SourceCode/Bond/Servo/CGlass.cpp b/SourceCode/Bond/Servo/CGlass.cpp
index ae2bca0..9220291 100644
--- a/SourceCode/Bond/Servo/CGlass.cpp
+++ b/SourceCode/Bond/Servo/CGlass.cpp
@@ -1,4 +1,4 @@
-#include "stdafx.h"
+锘�#include "stdafx.h"
 #include "CGlass.h"
 #include "Log.h"
 
@@ -384,7 +384,7 @@
 		if (s.size() > maxLen) s.resize(maxLen);
 	}
 
-	// —— 时间戳 & 工具 —— 
+	// 鐘舵�佹椂闂存埑锛氭帓闃�/寮�濮�/缁撴潫
 	void CGlass::markQueued() 
 	{
 		m_state = GlsState::Queued;
@@ -432,29 +432,44 @@
 		return strOut;
 	}
 
-	// ========== SV数据管理接口实现 ==========
-
+	// ========== SV鏁版嵁鍙h ==========
+	static constexpr size_t MAX_SV_DATA_KEEP = 4800;
 	void CGlass::addSVData(int machineId, const std::string& dataType, const SVDataItem& dataItem) {
-		m_svDatas[machineId][dataType].push_back(dataItem);
+		auto& vec = m_svDatas[machineId][dataType];
+		vec.push_back(dataItem);
+		if (vec.size() > MAX_SV_DATA_KEEP) {
+			vec.erase(vec.begin(), vec.begin() + (vec.size() - MAX_SV_DATA_KEEP));
+		}
 	}
 
 	void CGlass::addSVData(int machineId, const std::string& dataType, double value) {
 		auto now = std::chrono::system_clock::now();
-		m_svDatas[machineId][dataType].emplace_back(now, value);
+		auto& vec = m_svDatas[machineId][dataType];
+		vec.emplace_back(now, value);
+		if (vec.size() > MAX_SV_DATA_KEEP) {
+			vec.erase(vec.begin(), vec.begin() + (vec.size() - MAX_SV_DATA_KEEP));
+		}
 	}
 
 	void CGlass::addSVData(int machineId, const std::string& dataType, int64_t timestamp, double value) {
-		// 将int64_t时间戳转换为system_clock::time_point
+		// int64_t鏃堕棿杞垚system_clock::time_point
 		std::chrono::system_clock::time_point timePoint{
-			std::chrono::milliseconds(timestamp)  // 假设timestamp是毫秒
-			// 如果是秒,使用:std::chrono::seconds(timestamp)
+			std::chrono::milliseconds(timestamp)  // timestamp绮惧害锛氭绉�
+			// 濡傛灉闇�瑕佺簿搴︽洿楂橈紝鍙兘瑕佷娇鐢ㄥ叾浠栨椂闂村崟浣嶏紝濡俿td::chrono::seconds(timestamp)
 		};
-		m_svDatas[machineId][dataType].emplace_back(timePoint, value);
+		auto& vec = m_svDatas[machineId][dataType];
+		vec.emplace_back(timePoint, value);
+		if (vec.size() > MAX_SV_DATA_KEEP) {
+			vec.erase(vec.begin(), vec.begin() + (vec.size() - MAX_SV_DATA_KEEP));
+		}
 	}
 
 	void CGlass::addSVData(int machineId, const std::string& dataType, const std::vector<SVDataItem>& dataItems) {
 		auto& dataList = m_svDatas[machineId][dataType];
 		dataList.insert(dataList.end(), dataItems.begin(), dataItems.end());
+		if (dataList.size() > MAX_SV_DATA_KEEP) {
+			dataList.erase(dataList.begin(), dataList.begin() + (dataList.size() - MAX_SV_DATA_KEEP));
+		}
 	}
 
 	std::vector<SVDataItem> CGlass::getSVData(int machineId, const std::string& dataType) const {
@@ -515,7 +530,6 @@
 		auto machineIt = m_svDatas.find(machineId);
 		if (machineIt != m_svDatas.end()) {
 			machineIt->second.erase(dataType);
-			// 如果该机器没有其他数据了,也清除机器条目
 			if (machineIt->second.empty()) {
 				m_svDatas.erase(machineIt);
 			}
diff --git a/SourceCode/Bond/Servo/CHMPropertyDlg.cpp b/SourceCode/Bond/Servo/CHMPropertyDlg.cpp
index 2542fca..59cce8a 100644
--- a/SourceCode/Bond/Servo/CHMPropertyDlg.cpp
+++ b/SourceCode/Bond/Servo/CHMPropertyDlg.cpp
@@ -5,7 +5,7 @@
 #include "Servo.h"
 #include "CHMPropertyDlg.h"
 #include "afxdialogex.h"
-#include "HmTab.h"
+#include <algorithm>
 
 
 // CEquipmentDlg 瀵硅瘽妗�
@@ -19,6 +19,7 @@
 	m_hbrBkgnd = nullptr;
 	m_nWndWidth = 0;
 	m_nWndHeight = 0;
+	m_pTab = nullptr;
 }
 
 CHMPropertyDlg::CHMPropertyDlg(const char* pszTitle, int width, int height)
@@ -29,6 +30,7 @@
 	m_nWndWidth = width;
 	m_nWndHeight = height;
 	m_strTitle = pszTitle;
+	m_pTab = nullptr;
 }
 
 CHMPropertyDlg::~CHMPropertyDlg()
@@ -74,12 +76,13 @@
 
 	// Tab
 	CString strTitle;
-	CHmTab* m_pTab = CHmTab::Hook(GetDlgItem(IDC_TAB1)->m_hWnd);
+	m_pTab = CHmTab::Hook(GetDlgItem(IDC_TAB1)->m_hWnd);
 	m_pTab->SetPaddingLeft(20);
 	m_pTab->SetItemMarginLeft(18);
 	for (int i = 0; i < m_pages.size(); i++) {
 		m_pages[i]->SetParent(this);
 		m_pages[i]->GetWindowText(strTitle);
+		m_pages[i]->OnCreateBtns();
 		m_pTab->AddItem(strTitle, i == m_pages.size() - 1);
 	}
 	m_pTab->SetCurSel(0);
@@ -125,7 +128,7 @@
 void CHMPropertyDlg::OnSize(UINT nType, int cx, int cy)
 {
 	CDialogEx::OnSize(nType, cx, cy);
-	if (GetDlgItem(IDC_TAB1) == nullptr) return;
+	if (m_pTab == nullptr) return;
 	Resize();
 }
 
@@ -157,8 +160,36 @@
 	pItem->GetWindowRect(&rcItem);
 	pItem->MoveWindow(x2 - rcItem.Width(), y2 - rcItem.Height(),
 		rcItem.Width(), rcItem.Height());
-	y2 -= rcItem.Height();
-	y2 -= 12;
+
+	// 褰撳墠瀛愰〉鎸夐挳锛堝鏋滄湁锛�
+	int btnY = y2 - rcItem.Height();
+	int btnX = 12;
+	y2 -= rcItem.Height() + 12;
+	int curIndex = (m_pTab != nullptr) ? m_pTab->GetCurSel() : 0;
+	if (curIndex >= 0 && curIndex < (int)m_pages.size()) {
+		auto& btnMap = m_pages[curIndex]->getBtns();
+		// 鎸� BTN_ORDER 鎺掑簭
+		std::vector<std::pair<int, CButton*>> ordered;
+		for (auto& kv : btnMap) {
+			CButton* btn = kv.second;
+			if (btn == nullptr || !::IsWindow(btn->GetSafeHwnd())) continue;
+			int order = (int)(INT_PTR)::GetProp(btn->GetSafeHwnd(), _T("BTN_ORDER"));
+			ordered.emplace_back(order, btn);
+		}
+		std::sort(ordered.begin(), ordered.end(), [](const auto& a, const auto& b) {
+			return a.first < b.first;
+		});
+		for (auto& item : ordered) {
+			CButton* btn = item.second;
+			CRect rcBtn;
+			btn->GetWindowRect(&rcBtn);
+			if (rcBtn.Width() <= 0 || rcBtn.Height() <= 0) {
+				rcBtn.SetRect(0, 0, 80, 28);
+			}
+			btn->MoveWindow(btnX, btnY, rcBtn.Width(), rcBtn.Height());
+			btnX += rcBtn.Width() + 8;
+		}
+	}
 
 	// 鍒嗛殧绾�
 	pItem = GetDlgItem(IDC_LINE1);
@@ -185,6 +216,50 @@
 	for (int i = 0; i < m_pages.size(); i++) {
 		m_pages[i]->ShowWindow(i == index ? SW_SHOW : SW_HIDE);
 	}
+
+	// 闅愯棌鎵�鏈夐〉闈㈢殑鎸夐挳
+	for (auto page : m_pages) {
+		for (auto& kv : page->getBtns()) {
+			CButton* btn = kv.second;
+			if (btn != nullptr && ::IsWindow(btn->GetSafeHwnd())) {
+				btn->ShowWindow(SW_HIDE);
+			}
+		}
+	}
+
+	// 鍒涘缓骞舵樉绀哄綋鍓嶉〉闈㈢殑鎸夐挳
+	auto& btns = m_pages[index]->getBtns();
+	if (!btns.empty()) {
+		CRect rcClient;
+		GetClientRect(&rcClient);
+		const int margin = 12;
+		const int spacing = 8;
+		int x = margin;
+		int y = rcClient.bottom - 40; // 棰勭暀搴曢儴鍖哄煙
+
+		// 鎸� BTN_ORDER 鎺掑簭
+		std::vector<std::pair<int, CButton*>> ordered;
+		for (auto& kv : btns) {
+			CButton* btn = kv.second;
+			if (btn == nullptr || !::IsWindow(btn->GetSafeHwnd())) continue;
+			int order = (int)(INT_PTR)::GetProp(btn->GetSafeHwnd(), _T("BTN_ORDER"));
+			ordered.emplace_back(order, btn);
+		}
+		std::sort(ordered.begin(), ordered.end(), [](const auto& a, const auto& b) {
+			return a.first < b.first;
+		});
+		for (auto& item : ordered) {
+			CButton* btn = item.second;
+			CRect rc;
+			btn->GetWindowRect(&rc);
+			if (rc.Width() <= 0 || rc.Height() <= 0) {
+				rc.SetRect(0, 0, 80, 28);
+			}
+			btn->MoveWindow(x, y, rc.Width(), rc.Height());
+			btn->ShowWindow(SW_SHOW);
+			x += rc.Width() + spacing;
+		}
+	}
 }
 
 void CHMPropertyDlg::SetWindowSize(int width, int height)
@@ -210,3 +285,22 @@
 		}
 	}
 }
+
+BOOL CHMPropertyDlg::OnCommand(WPARAM wParam, LPARAM lParam)
+{
+	UINT code = HIWORD(wParam);
+	HWND hCtrl = (HWND)lParam;
+
+	if (code == BN_CLICKED && hCtrl != nullptr) {
+		for (auto page : m_pages) {
+			for (auto& kv : page->getBtns()) {
+				if (kv.second != nullptr && kv.second->GetSafeHwnd() == hCtrl) {
+					page->HandleBtnClick(hCtrl);
+					return TRUE;
+				}
+			}
+		}
+	}
+
+	return CDialogEx::OnCommand(wParam, lParam);
+}
diff --git a/SourceCode/Bond/Servo/CHMPropertyDlg.h b/SourceCode/Bond/Servo/CHMPropertyDlg.h
index 9ff65b1..43b8f25 100644
--- a/SourceCode/Bond/Servo/CHMPropertyDlg.h
+++ b/SourceCode/Bond/Servo/CHMPropertyDlg.h
@@ -1,6 +1,7 @@
 锘�#pragma once
 #include <vector>
 #include "CHMPropertyPage.h"
+#include "HmTab.h"
 
 
 // CEquipmentDlg 瀵硅瘽妗�
@@ -30,6 +31,7 @@
 	std::vector<CHMPropertyPage*> m_pages;
 	int m_nWndWidth;
 	int m_nWndHeight;
+	CHmTab* m_pTab;
 
 
 // 瀵硅瘽妗嗘暟鎹�
@@ -49,4 +51,5 @@
 	afx_msg void OnTabSelChanged(NMHDR* nmhdr, LRESULT* result);
 	afx_msg void OnBnClickedOk();
 	afx_msg void OnBnClickedButtonApply();
+	virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
 };
diff --git a/SourceCode/Bond/Servo/CHMPropertyPage.cpp b/SourceCode/Bond/Servo/CHMPropertyPage.cpp
index 3591ec4..f5626b6 100644
--- a/SourceCode/Bond/Servo/CHMPropertyPage.cpp
+++ b/SourceCode/Bond/Servo/CHMPropertyPage.cpp
@@ -18,3 +18,75 @@
 {
 
 }
+
+void CHMPropertyPage::OnCreateBtns()
+{
+
+}
+
+CButton* CHMPropertyPage::CreateBtn(const char* name, int w, int h, const UINT id)
+{
+	std::string key = std::string(name);
+	auto it = m_btns.find(key);
+	if (it != m_btns.end()) {
+		return it->second;
+	}
+
+	CButton* pBtn = new CButton();
+	pBtn->Create(name, WS_CHILD, CRect(0, 0, w, h), GetParent(), id);
+	// 浣跨敤榛樿GUI瀛椾綋
+	HFONT hFont = (HFONT)::GetStockObject(DEFAULT_GUI_FONT);
+	if (hFont != nullptr) {
+		pBtn->SetFont(CFont::FromHandle(hFont), FALSE);
+	}
+	::SetProp(pBtn->GetSafeHwnd(), _T("BTN_ORDER"), (HANDLE)(INT_PTR)m_btnOrderSeq++);
+	m_btns[key] = pBtn;
+	return pBtn;
+}
+
+CButton* CHMPropertyPage::GetBtnByName(const char* name)
+{
+	auto it = m_btns.find(std::string(name));
+	if (it != m_btns.end()) {
+		return it->second;
+	}
+	return nullptr;
+}
+
+std::map<std::string, CButton*>& CHMPropertyPage::getBtns()
+{
+	return m_btns;
+}
+
+void CHMPropertyPage::HandleBtnClick(HWND hBtn)
+{
+	for (auto& kv : m_btns) {
+		if (kv.second != nullptr && kv.second->GetSafeHwnd() == hBtn) {
+			OnClickedBtn(kv.first.c_str());
+			break;
+		}
+	}
+}
+
+BEGIN_MESSAGE_MAP(CHMPropertyPage, CDialogEx)
+	ON_WM_DESTROY()
+END_MESSAGE_MAP()
+
+void CHMPropertyPage::OnDestroy()
+{
+	CDialogEx::OnDestroy();
+
+	for (auto& kv : m_btns) {
+		CButton* btn = kv.second;
+		if (btn != nullptr) {
+			if (::IsWindow(btn->GetSafeHwnd())) {
+				::RemoveProp(btn->GetSafeHwnd(), _T("BTN_ORDER"));
+			}
+			if (::IsWindow(btn->GetSafeHwnd())) {
+				btn->DestroyWindow();
+			}
+			delete btn;
+		}
+	}
+	m_btns.clear();
+}
diff --git a/SourceCode/Bond/Servo/CHMPropertyPage.h b/SourceCode/Bond/Servo/CHMPropertyPage.h
index 8f90c5b..33e53b4 100644
--- a/SourceCode/Bond/Servo/CHMPropertyPage.h
+++ b/SourceCode/Bond/Servo/CHMPropertyPage.h
@@ -1,14 +1,29 @@
-#pragma once
+锘�#pragma once
+#include <map>
+#include <string>
 
 class CHMPropertyPage : public CDialogEx
 {
 	DECLARE_DYNAMIC(CHMPropertyPage)
 
 public:
-	CHMPropertyPage(UINT nID, CWnd* pPage);			// 标准构造函数
-	virtual ~CHMPropertyPage();						// 析构函数
+	CHMPropertyPage(UINT nID, CWnd* pPage);			// 鏍囧噯鏋勯�犲嚱鏁�
+	virtual ~CHMPropertyPage();						// 鏋愭瀯鍑芥暟
 	virtual void OnApply();
+	virtual void OnCreateBtns();
+	afx_msg void OnDestroy();
+	std::map<std::string, CButton*>& getBtns();
+	CButton* GetBtnByName(const char* name);
+	void HandleBtnClick(HWND hBtn);
 
+protected:
+	// 瀛愮被鍙噸鍐欙細鏂板/鍒犻櫎/缂栬緫鎸夐挳鐐瑰嚮澶勭悊
+	virtual void OnClickedBtn(const char* btnName) {};
 
+protected:
+	CButton* CreateBtn(const char* name, int w, int h, const UINT id);
+	std::map<std::string, CButton*> m_btns;
+	int m_btnOrderSeq{ 0 };
+
+	DECLARE_MESSAGE_MAP()
 };
-
diff --git a/SourceCode/Bond/Servo/CLoadPort.cpp b/SourceCode/Bond/Servo/CLoadPort.cpp
index ebd1690..4b86cf7 100644
--- a/SourceCode/Bond/Servo/CLoadPort.cpp
+++ b/SourceCode/Bond/Servo/CLoadPort.cpp
@@ -1,4 +1,4 @@
-#include "stdafx.h"
+锘�#include "stdafx.h"
 #include "CLoadPort.h"
 #include "CGlassPool.h"
 #include "Servo.h"
@@ -17,6 +17,7 @@
 		m_bAutoChangeEnable = FALSE;
 		m_nNextCassetteSequenceNo = 0;
 		m_isCompareMapsBeforeProceeding = FALSE;
+		m_downloadCassetteMap = 0;
 	}
 
 	CLoadPort::~CLoadPort()
@@ -40,16 +41,16 @@
 		CEquipment::term();
 	}
 
-	// 必须要实现的虚函数,在此初始化Pin列表
+	// 蹇呴』瑕佸疄鐜扮殑铏氬嚱鏁帮紝鍦ㄦ鍒濆鍖朠in鍒楄〃
 	void CLoadPort::initPins()
 	{
-		// 加入Pin初始化代码
+		// 鍔犲叆Pin鍒濆鍖栦唬鐮�
 		LOGD("<CLoadPort>initPins");
 		addPin(SERVO::PinType::INPUT, _T("In"));
 		addPin(SERVO::PinType::OUTPUT, _T("Out"));
 	}
 
-	// 必须要实现的虚函数,在此初始化Slot信息
+	// 蹇呴』瑕佸疄鐜扮殑铏氬嚱鏁帮紝鍦ㄦ鍒濆鍖朣lot淇℃伅
 	void CLoadPort::initSlots()
 	{
 		for (int i = 0; i < SLOT_MAX; i++) {
@@ -351,7 +352,7 @@
 		CEquipment::onTimer(nTimerid);
 
 
-		// 从配置读出的enable,初始化时写给efem
+		// 浠庨厤缃鍑虹殑enable锛屽垵濮嬪寲鏃跺啓缁檈fem
 		static int i_enable[4] = { 0 };
 		if ((++i_enable[m_nIndex]) == 20 + m_nIndex) {
 			eablePort(m_bEnable, [&](int code) -> int {
@@ -362,7 +363,7 @@
 
 
 
-		// 模拟测试
+		// 妯℃嫙娴嬭瘯
 		/*
 		if (m_nIndex == 0) {
 			static int ii = 0;
@@ -376,6 +377,7 @@
 				portStatusReport.setCassetteId("CID1001");
 				int nRet = portStatusReport.serialize(szBuffer, 64);
 				decodePortStatusReport(pStep, szBuffer, 64);
+				LOGI("<CLoadPort>Port1杞藉叆妯℃嫙鏁版嵁锛� id:CID1001 map: 0xf");
 			}
 		}
 		if (m_nIndex == 1) {
@@ -390,6 +392,7 @@
 				portStatusReport.setCassetteId("CID1004");
 				int nRet = portStatusReport.serialize(szBuffer, 64);
 				decodePortStatusReport(pStep, szBuffer, 64);
+				LOGI("<CLoadPort>Port2杞藉叆妯℃嫙鏁版嵁锛� id:CID1004 map: 0xff");
 			}
 		}
 		*/
@@ -558,6 +561,11 @@
 	std::string& CLoadPort::getCassetteId()
 	{
 		return m_portStatusReport.getCassetteId();
+	}
+
+	void CLoadPort::simulateSetCassetteId(const char* pszCarrierId)
+	{
+		m_portStatusReport.setCassetteId(pszCarrierId);
 	}
 
 	int CLoadPort::getLoadingCassetteType()
@@ -934,27 +942,15 @@
 		m_portStatusReport.copyEx(portStatusReport);
 
 
-		// 当port状态为InUse, 比较map
+		// 褰損ort鐘舵�佷负InUse, 姣旇緝map
 		if (m_portStatusReport.getPortStatus() == PORT_INUSE) {
-			if (m_isCompareMapsBeforeProceeding) {
-				short scanMap = getScanCassetteMap();
-				short downloadMap = getDownloadCassetteMap();
-				if (scanMap == downloadMap) {
-					generateGlassList(scanMap);
-					this->sendCassetteCtrlCmd(CCC_PROCESS_START, nullptr, 0, 0, 0, nullptr, nullptr);
-				}
-				else {
-					this->sendCassetteCtrlCmd(CCC_PROCESS_CANCEL, nullptr, 0, 0, 0, nullptr, nullptr);
+			// 鐢熸垚鐜荤拑鍒楄〃锛氭潵鑷� EFEM 鎵弿鍒扮殑 map
+			generateGlassList(getScanCassetteMap());
 
-					// 抛出到应用层做提示
-					if (m_listener.onMapMismatch != nullptr) {
-						m_listener.onMapMismatch(this, scanMap, downloadMap);
-					}
-				}
-			}
-			else {
-				// 抛出到应用层做选择要加工的片子
-				generateGlassList(getScanCassetteMap());
+			// CompareMapsBeforeProceeding锛氫笉鍦ㄦ澶勮嚜鍔� Start/Cancel锛屾敼涓虹瓑寰� Host 鍐崇瓥锛圥roceedWithCarrier/ProceedWithSlotMap/CarrierRelease锛�
+			// Host 鍐崇瓥鍏ュ彛锛歋3F17 CarrierAction -> listener.onCarrierAction -> CMaster::proceedWithCarrier()/carrierRelease()
+			if (m_isCompareMapsBeforeProceeding) {
+				// 杩欓噷浠呯瓑寰咃紝鍏蜂綋涓婃姤鐢变笂灞傚湪 PORT_INUSE 浜嬩欢涓Е鍙戯紙S6F11 CheckSlotMap锛�
 			}
 		}
 		if (m_listener.onPortStatusChanged != nullptr) {
@@ -963,7 +959,7 @@
 		}
 
 
-		// 缓存Attribute,用于调试时显示信息
+		// 缂撳瓨Attribute锛岀敤浜庤皟璇曟椂鏄剧ず淇℃伅
 		unsigned int weight = 201;
 		CAttributeVector& attrubutes = pStep->attributeVector();
 		m_portStatusReport.getAttributeVector(attrubutes, weight);
@@ -988,17 +984,17 @@
 			return -1;
 		}
 
-		LOGI("<CLoadPort-%d>准备设置Port type<%d>", m_nIndex, (int)type);
+		LOGI("<CLoadPort-%d>鍑嗗璁剧疆Port type<%d>", m_nIndex, (int)type);
 		short value = (short)type;
 		pStep->writeDataEx((const char*)&value, sizeof(short), [&, onWritedBlock](int code) -> int {
 			// test
 			code = WOK;
 			if (code == WOK) {
 				m_portType = type;
-				LOGI("<CLoadPort-%d>设置Port type成功.", m_nIndex);
+				LOGI("<CLoadPort-%d>璁剧疆Port type鎴愬姛.", m_nIndex);
 			}
 			else {
-				LOGE("<CLoadPort-%d>设置Port type失败,code:%d", m_nIndex, code);
+				LOGE("<CLoadPort-%d>璁剧疆Port type澶辫触锛宑ode:%d", m_nIndex, code);
 			}
 			if (onWritedBlock != nullptr) {
 				return onWritedBlock(code);
@@ -1018,17 +1014,17 @@
 			return -1;
 		}
 
-		LOGI("<CLoadPort-%d>准备%s Port", m_nIndex, bEnable ? _T("启用") : _T("禁用"));
+		LOGI("<CLoadPort-%d>鍑嗗%s Port", m_nIndex, bEnable ? _T("鍚敤") : _T("绂佺敤"));
 		short value = bEnable ? 1 : 2;
 		pStep->writeDataEx((const char*)&value, sizeof(short), [&, onWritedBlock](int code) -> int {
 			// test
 			code = WOK;
 			if (code == WOK) {
 				m_bEnable = bEnable;
-				LOGI("<CLoadPort-%d>%s Port成功.", m_nIndex, bEnable ? _T("启用") : _T("禁用"));
+				LOGI("<CLoadPort-%d>%s Port鎴愬姛.", m_nIndex, bEnable ? _T("鍚敤") : _T("绂佺敤"));
 			}
 			else {
-				LOGE("<CLoadPort-%d>%s  Port失败,code:%d", m_nIndex, bEnable ? _T("启用") : _T("禁用"), code);
+				LOGE("<CLoadPort-%d>%s  Port澶辫触锛宑ode:%d", m_nIndex, bEnable ? _T("鍚敤") : _T("绂佺敤"), code);
 			}
 			if (onWritedBlock != nullptr) {
 				return onWritedBlock(code);
@@ -1047,17 +1043,17 @@
 			return -1;
 		}
 
-		LOGI("<CLoadPort-%d>准备设置Port mode<%d>", m_nIndex, (int)mode);
+		LOGI("<CLoadPort-%d>鍑嗗璁剧疆Port mode<%d>", m_nIndex, (int)mode);
 		short value = (short)mode;
 		pStep->writeDataEx((const char*)&value, sizeof(short), [&, onWritedBlock](int code) -> int {
 			// test
 			code = WOK;
 			if (code == WOK) {
 				m_portMode = mode;
-				LOGI("<CLoadPort-%d>设置Port mode成功.", m_nIndex);
+				LOGI("<CLoadPort-%d>璁剧疆Port mode鎴愬姛.", m_nIndex);
 			}
 			else {
-				LOGE("<CLoadPort-%d>设置Port mode失败,code:%d", m_nIndex, code);
+				LOGE("<CLoadPort-%d>璁剧疆Port mode澶辫触锛宑ode:%d", m_nIndex, code);
 			}
 			if (onWritedBlock != nullptr) {
 				return onWritedBlock(code);
@@ -1077,16 +1073,16 @@
 			return -1;
 		}
 
-		LOGI("<CLoadPort-%d>准备设置Cassette Type<%d>", m_nIndex, (int)type);
+		LOGI("<CLoadPort-%d>鍑嗗璁剧疆Cassette Type<%d>", m_nIndex, (int)type);
 		short value = (short)type;
 		pStep->writeDataEx((const char*)&value, sizeof(short), [&, onWritedBlock](int code) -> int {
 			// test
 			code = WOK;
 			if (code == WOK) {
-				LOGI("<CLoadPort-%d>设置Cassette Type成功.", m_nIndex);
+				LOGI("<CLoadPort-%d>璁剧疆Cassette Type鎴愬姛.", m_nIndex);
 			}
 			else {
-				LOGE("<CLoadPort-%d>设置Cassette Type失败,code:%d", m_nIndex, code);
+				LOGE("<CLoadPort-%d>璁剧疆Cassette Type澶辫触锛宑ode:%d", m_nIndex, code);
 			}
 			if (onWritedBlock != nullptr) {
 				return onWritedBlock(code);
@@ -1105,17 +1101,17 @@
 			return -1;
 		}
 
-		LOGI("<CLoadPort-%d>准备设置Transfer mode<%d>", m_nIndex, (int)mode);
+		LOGI("<CLoadPort-%d>鍑嗗璁剧疆Transfer mode<%d>", m_nIndex, (int)mode);
 		short value = (short)mode;
 		pStep->writeDataEx((const char*)&value, sizeof(short), [&, onWritedBlock](int code) -> int {
 			// test
 			code = WOK;
 			if (code == WOK) {
 				m_transferMode = mode;
-				LOGI("<CLoadPort-%d>设置Transfer mode成功.", m_nIndex + 1);
+				LOGI("<CLoadPort-%d>璁剧疆Transfer mode鎴愬姛.", m_nIndex + 1);
 			}
 			else {
-				LOGE("<CLoadPort-%d>设置Transfer mode失败,code:%d", m_nIndex + 1, code);
+				LOGE("<CLoadPort-%d>璁剧疆Transfer mode澶辫触锛宑ode:%d", m_nIndex + 1, code);
 			}
 			if (onWritedBlock != nullptr) {
 				return onWritedBlock(code);
@@ -1134,17 +1130,17 @@
 			return -1;
 		}
 
-		LOGI("<CLoadPort-%d>准备%s Auto Change", m_nIndex, bEnable ? _T("启用") : _T("禁用"));
+		LOGI("<CLoadPort-%d>鍑嗗%s Auto Change", m_nIndex, bEnable ? _T("鍚敤") : _T("绂佺敤"));
 		short value = bEnable ? 1 : 2;
 		pStep->writeDataEx((const char*)&value, sizeof(short), [&, onWritedBlock](int code) -> int {
 			// test
 			code = WOK;
 			if (code == WOK) {
 				m_bAutoChangeEnable = bEnable;
-				LOGI("<CLoadPort-%d>%s Auto Change成功.", m_nIndex, bEnable ? _T("启用") : _T("禁用"));
+				LOGI("<CLoadPort-%d>%s Auto Change鎴愬姛.", m_nIndex, bEnable ? _T("鍚敤") : _T("绂佺敤"));
 			}
 			else {
-				LOGE("<CLoadPort-%d>%s  Auto Change失败,code:%d", m_nIndex, bEnable ? _T("启用") : _T("禁用"), code);
+				LOGE("<CLoadPort-%d>%s  Auto Change澶辫触锛宑ode:%d", m_nIndex, bEnable ? _T("鍚敤") : _T("绂佺敤"), code);
 			}
 			if (onWritedBlock != nullptr) {
 				return onWritedBlock(code);
@@ -1192,17 +1188,20 @@
 
 	short CLoadPort::getDownloadCassetteMap()
 	{
-		// 暂时未实现此功能
-		short map = 0;
-		return map;
+		return m_downloadCassetteMap;
+	}
+
+	void CLoadPort::setDownloadCassetteMap(short map)
+	{
+		m_downloadCassetteMap = map;
 	}
 
 	/*
-	 * 生成测试用的玻璃列表
+	 * 鐢熸垚娴嬭瘯鐢ㄧ殑鐜荤拑鍒楄〃
 	 */
 	int CLoadPort::testGenerateGlassList(MaterialsType type)
 	{
-		// 如果非空就不生成了
+		// 濡傛灉闈炵┖灏变笉鐢熸垚浜�
 		Lock();
 		if (hasGlass()) {
 			Unlock();
@@ -1236,11 +1235,11 @@
 	}
 
 	/*
-	 * 根据efem扫描到的map,生成玻璃列表
+	 * 鏍规嵁efem鎵弿鍒扮殑map锛岀敓鎴愮幓鐠冨垪琛�
 	 */
 	int CLoadPort::generateGlassList(short map)
 	{
-		// 先释放较早前的数据
+		// 鍏堥噴鏀捐緝鏃╁墠鐨勬暟鎹�
 		Lock();
 		for (int i = 0; i < SLOT_MAX; i++) {
 			m_slot[i].setContext(nullptr);
@@ -1248,7 +1247,7 @@
 		Unlock();
 
 
-		// 根据map生成新的
+		// 鏍规嵁map鐢熸垚鏂扮殑
 		char szBuffer[64];
 		for (int i = 0; i < SLOT_MAX; i++) {
 			if (!m_slot[i].isEnable()) continue;
@@ -1320,4 +1319,9 @@
 	{
 		m_isCompareMapsBeforeProceeding = bCompare;
 	}
+
+	BOOL CLoadPort::isCompareMapsBeforeProceeding() const
+	{
+		return m_isCompareMapsBeforeProceeding;
+	}
 }
diff --git a/SourceCode/Bond/Servo/CLoadPort.h b/SourceCode/Bond/Servo/CLoadPort.h
index 523ab69..1fb70d7 100644
--- a/SourceCode/Bond/Servo/CLoadPort.h
+++ b/SourceCode/Bond/Servo/CLoadPort.h
@@ -1,4 +1,4 @@
-#pragma once
+锘�#pragma once
 #include "CEquipment.h"
 #include "ServoCommo.h"
 
@@ -39,6 +39,7 @@
 		void localAutoChangeEnable(BOOL bEnable);
 		short getScanCassetteMap();
 		short getDownloadCassetteMap();
+		void setDownloadCassetteMap(short map);
 
 	public:
 		short getNextCassetteSequenceNo();
@@ -55,6 +56,8 @@
 		int getPortStatus();
 		int getCassetteSequenceNo();
 		std::string& getCassetteId();
+		// Simulation helper: allow setting CarrierID when no EFEM is connected.
+		void simulateSetCassetteId(const char* pszCarrierId);
 		int getLoadingCassetteType();
 		int getQTimeFlag();
 		int getCassetteMappingState();
@@ -85,6 +88,7 @@
 			ONWRITED onWritedBlock);
 		CStep* getCassetteCtrlCmdStep();
 		void setCompareMapsBeforeProceeding(BOOL bCompare);
+		BOOL isCompareMapsBeforeProceeding() const;
 
 	private:
 		int decodePortStatusReport(CStep* pStep, const char* pszData, size_t size);
@@ -100,8 +104,8 @@
 		CPortStatusReport m_portStatusReport;
 		int m_nNextCassetteSequenceNo;
 
-		// 在开始工艺前是否先需要先比较map
+		// 鍦ㄥ紑濮嬪伐鑹哄墠鏄惁鍏堥渶瑕佸厛姣旇緝map
 		BOOL m_isCompareMapsBeforeProceeding;
+		short m_downloadCassetteMap;
 	};
 }
-
diff --git a/SourceCode/Bond/Servo/CMaster.cpp b/SourceCode/Bond/Servo/CMaster.cpp
index d36f540..bd2e9ab 100644
--- a/SourceCode/Bond/Servo/CMaster.cpp
+++ b/SourceCode/Bond/Servo/CMaster.cpp
@@ -1,12 +1,16 @@
-#include "stdafx.h"
+锘�#include "stdafx.h"
 #include "Common.h"
 #include "CMaster.h"
 #include <future>
 #include <vector>
+#include <algorithm>
 #include "RecipeManager.h"
 #include <fstream>
 #include "SerializeUtil.h"
 #include "CServoUtilsTool.h"
+#include "AlarmManager.h"
+#include "ToolUnits.h"
+#include "Model.h"
 
 
 namespace SERVO {
@@ -58,6 +62,7 @@
 		m_ullStartTime = 0;
 		m_ullRunTime = 0;
 		m_state = MASTERSTATE::READY;
+		m_curveMode = CurveMode::Production;
 		m_pActiveRobotTask = nullptr;
 		m_nLastError = ER_CODE_NOERROR;
 		m_isCompareMapsBeforeProceeding = FALSE;
@@ -71,13 +76,15 @@
 		m_nContinuousWorkingPort = 0;
 		m_nContinuousWorkingSlot = 0;
 		m_pControlJob = nullptr;
+		m_bPauseAlarmRaised = false;
+		m_pModelCtx = nullptr;
 		m_nTestFlag = 0;
 		InitializeCriticalSection(&m_criticalSection);
 	}
 
 	CMaster::~CMaster()
 	{
-		// 释放Job相关
+		// 閲婃斁Job鐩稿叧
 		for (auto item : m_processJobs) {
 			delete item;
 		}
@@ -112,12 +119,18 @@
 			m_hEventDispatchThreadExit[1] = nullptr;
 		}
 
+
 		DeleteCriticalSection(&m_criticalSection);
 	}
 
 	void CMaster::setListener(MasterListener listener)
 	{
 		m_listener = listener;
+	}
+
+	void CMaster::setModelCtx(CModel* pModel)
+	{
+		m_pModelCtx = pModel;
 	}
 
 	CRobotTask* CMaster::getActiveRobotTask()
@@ -127,36 +140,43 @@
 
 	int CMaster::init()
 	{
-		LOGI("<Master>正在初始化...");
+		const ULONGLONG boot_master_begin = GetTickCount64();
+		LOGI("<Master>姝e湪鍒濆鍖�...");
+		LOGI("[BOOT][MASTER] init begin");
 
 
 		// 	cclink
-		if (m_cclink.Connect(CC_LINK_IE_CONTROL_CHANNEL(1)) != 0) {
-			LOGE("连接CC-Link失败.");
+		const ULONGLONG boot_cclink_begin = GetTickCount64();
+		const int cc_ret = m_cclink.Connect(CC_LINK_IE_CONTROL_CHANNEL(1));
+		LOGI("[BOOT][MASTER] CC-Link connect ret=%d, cost=%llu ms",
+			cc_ret,
+			(unsigned long long)(GetTickCount64() - boot_cclink_begin));
+		if (cc_ret != 0) {
+			LOGE("杩炴帴CC-Link澶辫触.");
 		}
 		else {
-			LOGI("连接CC-Link成功.");
+			LOGI("杩炴帴CC-Link鎴愬姛.");
 			BoardVersion version{};
 			int nRet = m_cclink.GetBoardVersion(version);
 			if (nRet == 0) {
-				LOGD("版本信息:%s.", version.toString().c_str());
+				LOGD("鐗堟湰淇℃伅锛�%s.", version.toString().c_str());
 			}
 			else {
-				LOGE("获取CC-Link版本信息失败.");
+				LOGE("鑾峰彇CC-Link鐗堟湰淇℃伅澶辫触.");
 			}
 
 			BoardStatus status;
 			nRet = m_cclink.GetBoardStatus(status);
 			if (nRet == 0) {
-				LOGD("状态:%s.", status.toString().c_str());
+				LOGD("鐘舵�侊細%s.", status.toString().c_str());
 			}
 			else {
-				LOGE("获取CC-Link状态失败.");
+				LOGE("鑾峰彇CC-Link鐘舵�佸け璐�.");
 			}
 		}
 
 
-		// 初始化添加各子设备
+		// 鍒濆鍖栨坊鍔犲悇瀛愯澶�
 		CLoadPort* pPort1, * pPort2, * pPort3, * pPort4;
 		CBonder* pBonder1, * pBonder2;
 		CEFEM* pEfem;
@@ -219,32 +239,77 @@
 
 
 
-		// 读缓存数据
+		// 璇荤紦瀛樻暟鎹�
+		const ULONGLONG boot_cache_begin = GetTickCount64();
+		const ULONGLONG boot_read_begin = GetTickCount64();
 		readCache();
+		LOGI("[BOOT][MASTER] readCache finished, cost=%llu ms", (unsigned long long)(GetTickCount64() - boot_read_begin));
+
+		const ULONGLONG boot_state_begin = GetTickCount64();
 		loadState();
+		LOGI("[BOOT][MASTER] loadState finished, cost=%llu ms", (unsigned long long)(GetTickCount64() - boot_state_begin));
+		if (m_listener.onControlJobChanged) {
+			notifyControlJobChanged();
+		}
+
+		LOGI("[BOOT][MASTER] cache/state loaded, cost=%llu ms (since init %llu ms)",
+			(unsigned long long)(GetTickCount64() - boot_cache_begin),
+			(unsigned long long)(GetTickCount64() - boot_master_begin));
 
 
-		// 定时器
+		// 瀹氭椂鍣�
 		g_pMaster = this;
 		SetTimer(NULL, 1, 250, (TIMERPROC)MasterTimerProc);
 
 
-		// 调度线程
+		// 璋冨害绾跨▼
 		m_hDispatchThreadHandle = (HANDLE)_beginthreadex(NULL, 0, SERVO::DispatchThreadFunction, this,
 			0, &m_nDispatchThreadAddr);
 
 
-		// 监控bit线程
+		// 鐩戞帶bit绾跨▼
 		m_hReadBitsThreadHandle = (HANDLE)_beginthreadex(NULL, 0, SERVO::ReadBitsThreadFunction, this,
 			0, &m_nReadBitsThreadAddr);
 
 
-		// 曲线服务
+		// 鏇茬嚎鏈嶅姟
 		CreateDAQBridgeServer();
 
 
-		LOGI("<Master>初始化完成.");
+		LOGI("<Master>鍒濆鍖栧畬鎴�.");
+		LOGI("[BOOT][MASTER] init finished, total cost=%llu ms",
+			(unsigned long long)(GetTickCount64() - boot_master_begin));
 		return 0;
+	}
+
+	void CMaster::setCurveMode(CurveMode mode)
+	{
+		if (m_curveMode == mode) {
+			return;
+		}
+		m_curveMode = mode;
+		if (m_pCollector != nullptr) {
+			const uint32_t mids[] = {
+				MID_Bonder1, MID_Bonder2,
+				MID_VacuumBakeA, MID_VacuumBakeB,
+				MID_BakeCoolingA, MID_BakeCoolingB
+			};
+			for (uint32_t mid : mids) {
+				if (mode == CurveMode::EmptyChamber) {
+					m_pCollector->batchStart(mid, "EMPTY_CHAMBER", 30 * 60 * 1000ULL); // 绌鸿厰妯″紡锛氬惎鍔ㄩ噰鏍锋壒娆�
+				}
+				else {
+					m_pCollector->batchStop(mid);
+					m_pCollector->buffersClear(mid); // 鍒囧洖鐢熶骇妯″紡锛屾竻鎺夌┖鑵旀暟鎹�
+				}
+			}
+		}
+		LOGI("<Master>CurveMode=%s", mode == CurveMode::EmptyChamber ? "EmptyChamber" : "Production");
+	}
+
+	CurveMode CMaster::getCurveMode() const
+	{
+		return m_curveMode;
 	}
 
 	int CMaster::term()
@@ -254,7 +319,7 @@
 		::WaitForSingleObject(m_hEventReadBitsThreadExit[1], INFINITE);
 		::WaitForSingleObject(m_hEventDispatchThreadExit[1], INFINITE);
 
-		LOGI("<Master>正在结束程序.");
+		LOGI("<Master>姝e湪缁撴潫绋嬪簭.");
 		for (auto item : m_listEquipment) {
 			item->term();
 		}
@@ -272,6 +337,13 @@
 		}
 		m_listEquipment.clear();
 
+		// release manual-remove buffer before glass pool is torn down
+		for (auto* pGlass : m_bufGlass) {
+			if (pGlass != nullptr) {
+				pGlass->release();
+			}
+		}
+		m_bufGlass.clear();
 
 		if (m_pCollector != nullptr) {
 			m_pCollector->stopLoop();
@@ -326,7 +398,7 @@
 
 	int CMaster::stop(int nErCode/* = ER_CODE_NOERROR*/)
 	{
-		// 运行时间为累加结果,本次停止时刷新;
+		// 杩愯鏃堕棿涓虹疮鍔犵粨鏋滐紝鏈鍋滄鏃跺埛鏂帮紱
 		lock();
 		if (m_state != MASTERSTATE::RUNNING && m_state != MASTERSTATE::RUNNING_CONTINUOUS_TRANSFER
 			&& m_state != MASTERSTATE::RUNNING_BATCH) {
@@ -337,12 +409,12 @@
 		unlock();
 
 
-		// 更新状态
+		// 鏇存柊鐘舵��
 		m_nLastError = nErCode;
 		setState(MASTERSTATE::STOPPING);
 
 
-		// ControlJob暂停
+		// ControlJob鏆傚仠
 		lock();
 		if (m_pControlJob != nullptr) {
 			m_pControlJob->pause();
@@ -377,14 +449,14 @@
 
 	unsigned CMaster::DispatchProc()
 	{
-		// 优先考虑的类型和次要类型
-		// 一种情况,如果不分主次,一直搬G1, 等到Bonder1和Bonder2都放了G1, Aligner也放了G1,
-		// Bonder1和Bonder2需要的G2就过不来了
-		// 最基本的实现,可以G2和G2轮流搬送,但最好根据Bonder的需求来决定
+		// 浼樺厛鑰冭檻鐨勭被鍨嬪拰娆¤绫诲瀷
+		// 涓�绉嶆儏鍐碉紝濡傛灉涓嶅垎涓绘锛屼竴鐩存惉G1, 绛夊埌Bonder1鍜孊onder2閮芥斁浜咷1, Aligner涔熸斁浜咷1,
+		// Bonder1鍜孊onder2闇�瑕佺殑G2灏辫繃涓嶆潵浜�
+		// 鏈�鍩烘湰鐨勫疄鐜帮紝鍙互G2鍜孏2杞祦鎼�侊紝浣嗘渶濂芥牴鎹瓸onder鐨勯渶姹傛潵鍐冲畾
 		MaterialsType primaryType, secondaryType;
 
 
-		// 各种机器
+		// 鍚勭鏈哄櫒
 		CLoadPort* pLoadPorts[4];
 		CEFEM* pEFEM = (CEFEM*)getEquipment(EQ_ID_EFEM);
 		pLoadPorts[0] = (CLoadPort*)getEquipment(EQ_ID_LOADPORT1);
@@ -413,7 +485,7 @@
 		ASSERT(pMeasurement);
 
 		while (1) {
-			// 待退出信号或时间到
+			// 寰呴��鍑轰俊鍙锋垨鏃堕棿鍒�
 			HANDLE hEvents[] = { m_hEventDispatchThreadExit[0], m_hDispatchEvent };
 			int nRet = WaitForMultipleObjects(2, hEvents, FALSE, 500);
 			if (nRet == WAIT_OBJECT_0) {
@@ -421,11 +493,11 @@
 			}
 			
 
-			// 如果状态为STARTING,开始工作并切换到RUNNING状态
+			// 濡傛灉鐘舵�佷负STARTING锛屽紑濮嬪伐浣滃苟鍒囨崲鍒癛UNNING鐘舵��
 			lock();
 			if (m_state == MASTERSTATE::STARTING) {
-				// 发送indexerOperationModeChange到各个机台,成功后切换到RUNNING状态
-				// 否则切换到MSERROR状态
+				// 鍙戦�乮ndexerOperationModeChange鍒板悇涓満鍙帮紝鎴愬姛鍚庡垏鎹㈠埌RUNNING鐘舵��
+				// 鍚﹀垯鍒囨崲鍒癕SERROR鐘舵��
 				int nRet;
 				CEquipment* pEq[6] = { pEFEM, pBonder1, pBonder2, pBakeCooling, 
 					pVacuumBake, pMeasurement};
@@ -440,9 +512,9 @@
 						TRACE("a0001\n", writeCode, retCode);
 					});
 				if (nRet != 0) {
-					LOGE("<Master>EFEM切换Start状态失败");
+					LOGE("<Master>EFEM鍒囨崲Start鐘舵�佸け璐�");
 					m_nLastError = ER_CODE_OPERATION_MODE_FAIL;
-					m_strLastError = "EFEM切换Start状态失败.";
+					m_strLastError = "EFEM鍒囨崲Start鐘舵�佸け璐�.";
 					goto WAIT;
 				}
 				futures.push_back(promises[0].get_future());
@@ -454,9 +526,9 @@
 						TRACE("a0002\n");
 					});
 				if (nRet != 0) {
-					LOGE("<Master>Bonder1切换Start状态失败");
+					LOGE("<Master>Bonder1鍒囨崲Start鐘舵�佸け璐�");
 					m_nLastError = ER_CODE_BONDER_OPERATION_MODE_FAIL;
-					m_strLastError = "Bonder1切换Start状态失败.";
+					m_strLastError = "Bonder1鍒囨崲Start鐘舵�佸け璐�.";
 					goto WAIT;
 				}
 				futures.push_back(promises[1].get_future());
@@ -468,9 +540,9 @@
 						TRACE("a0003\n");
 					});
 				if (nRet != 0) {
-					LOGE("<Master>Bonder2切换Start状态失败");
+					LOGE("<Master>Bonder2鍒囨崲Start鐘舵�佸け璐�");
 					m_nLastError = ER_CODE_BONDER_OPERATION_MODE_FAIL;
-					m_strLastError = "Bonder2切换Start状态失败.";
+					m_strLastError = "Bonder2鍒囨崲Start鐘舵�佸け璐�.";
 					goto WAIT;
 				}
 				futures.push_back(promises[2].get_future());
@@ -482,9 +554,9 @@
 						TRACE("a0004\n");
 					});
 				if (nRet != 0) {
-					LOGE("<Master>BakeCooling切换Start状态失败");
+					LOGE("<Master>BakeCooling鍒囨崲Start鐘舵�佸け璐�");
 					m_nLastError = ER_CODE_OPERATION_MODE_FAIL;
-					m_strLastError = "BakeCooling切换Start状态失败.";
+					m_strLastError = "BakeCooling鍒囨崲Start鐘舵�佸け璐�.";
 					goto WAIT;
 				}
 				futures.push_back(promises[3].get_future());
@@ -496,9 +568,9 @@
 						TRACE("a0005\n");
 					});
 				if (nRet != 0) {
-					LOGE("<Master>VacuumBake切换Start状态失败");
+					LOGE("<Master>VacuumBake鍒囨崲Start鐘舵�佸け璐�");
 					m_nLastError = ER_CODE_OPERATION_MODE_FAIL;
-					m_strLastError = "VacuumBake切换Start状态失败.";
+					m_strLastError = "VacuumBake鍒囨崲Start鐘舵�佸け璐�.";
 					goto WAIT;
 				}
 				futures.push_back(promises[4].get_future());
@@ -510,9 +582,9 @@
 						TRACE("a0006\n");
 					});
 				if (nRet != 0) {
-					LOGE("<Master>Measurement切换Start状态失败");
+					LOGE("<Master>Measurement鍒囨崲Start鐘舵�佸け璐�");
 					m_nLastError = ER_CODE_OPERATION_MODE_FAIL;
-					m_strLastError = "Measurement切换Start状态失败.";
+					m_strLastError = "Measurement鍒囨崲Start鐘舵�佸け璐�.";
 					goto WAIT;
 				}
 				futures.push_back(promises[5].get_future());
@@ -520,16 +592,16 @@
 
 WAIT:
 				for (auto& f : futures) {
-					f.wait();  // 阻塞等待对应设备完成
+					f.wait();  // 闃诲绛夊緟瀵瑰簲璁惧瀹屾垚
 				}
 				for (int i = 0; i < 6; i++) {
 					if (!bIomcOk[i]) {
 						bIomcOk[6] = FALSE;
-						LOGE("<Master>%s切换Start状态失败", pEq[i]->getName().c_str());
+						LOGE("<Master>%s鍒囨崲Start鐘舵�佸け璐�", pEq[i]->getName().c_str());
 					}
 				}
 				
-				// 检查看是否都已经切换到START状态
+				// 妫�鏌ョ湅鏄惁閮藉凡缁忓垏鎹㈠埌START鐘舵��
 				if (!bIomcOk[6]) {
 					unlock();
 					setState(MASTERSTATE::MSERROR);
@@ -548,10 +620,10 @@
 			}
 
 
-			// 处理完成当前事务后,切换到停止或就绪状态
+			// 澶勭悊瀹屾垚褰撳墠浜嬪姟鍚庯紝鍒囨崲鍒板仠姝㈡垨灏辩华鐘舵��
 			else if (m_state == MASTERSTATE::STOPPING) {
 				unlock();
-				LOGI("<Master>开始切换各设备到 Stop 模式...");
+				LOGI("<Master>寮�濮嬪垏鎹㈠悇璁惧鍒� Stop 妯″紡...");
 
 				std::vector<std::promise<void>> promises(6);
 				std::vector<std::future<void>> futures;
@@ -569,23 +641,23 @@
 							TRACE("s000%d: ret=%d\n", i + 1, retCode);
 						});
 					if (nRet != 0) {
-						LOGE("<Master>%s切换Stop状态发送失败", pEq[i]->getName().c_str());
+						LOGE("<Master>%s鍒囨崲Stop鐘舵�佸彂閫佸け璐�", pEq[i]->getName().c_str());
 						m_nLastError = ER_CODE_OPERATION_MODE_FAIL;
-						m_strLastError = pEq[i]->getName() + "切换Stop状态发送失败.";
+						m_strLastError = pEq[i]->getName() + "鍒囨崲Stop鐘舵�佸彂閫佸け璐�.";
 						bIomcOk[i] = FALSE;
-						promises[i].set_value(); // 避免 wait 阻塞
+						promises[i].set_value(); // 閬垮厤 wait 闃诲
 					}
 					futures.push_back(promises[i].get_future());
 				}
 
 				for (auto& f : futures) {
-					f.wait();  // 等待所有完成
+					f.wait();  // 绛夊緟鎵�鏈夊畬鎴�
 				}
 
 				for (int i = 0; i < 6; ++i) {
 					if (!bIomcOk[i]) {
 						bIomcOk[6] = FALSE;
-						LOGE("<Master>%s切换Stop状态失败", pEq[i]->getName().c_str());
+						LOGE("<Master>%s鍒囨崲Stop鐘舵�佸け璐�", pEq[i]->getName().c_str());
 					}
 				}
 
@@ -594,7 +666,7 @@
 					continue;
 				}
 
-				LOGI("<Master>所有设备成功切换到 Stop 模式");
+				LOGI("<Master>鎵�鏈夎澶囨垚鍔熷垏鎹㈠埌 Stop 妯″紡");
 				if(m_nLastError == ER_CODE_NOERROR)
 					setState(MASTERSTATE::READY);
 				else
@@ -604,9 +676,9 @@
 			}
 
 
-			// 调度逻辑处理
+			// 璋冨害閫昏緫澶勭悊
 			else if (m_state == MASTERSTATE::RUNNING) {
-				// 检测判断robot状态
+				// 妫�娴嬪垽鏂璻obot鐘舵��
 				RMDATA& rmd = pEFEM->getRobotMonitoringData();
 				if (rmd.status != ROBOT_STATUS::Idle && rmd.status != ROBOT_STATUS::Run) {
 					unlock();
@@ -618,13 +690,13 @@
 						m_pActiveRobotTask->place();
 					}
 					unlock();
-					// 检测到当前有正在下午的任务,确保当前任务完成或中止后继续
-					// LOGI("检测到当前有正在下午的任务,确保当前任务完成或中止后继续...");
+					// 妫�娴嬪埌褰撳墠鏈夋鍦ㄤ笅鍗堢殑浠诲姟锛岀‘淇濆綋鍓嶄换鍔″畬鎴愭垨涓鍚庣户缁�
+					// LOGI("妫�娴嬪埌褰撳墠鏈夋鍦ㄤ笅鍗堢殑浠诲姟锛岀‘淇濆綋鍓嶄换鍔″畬鎴愭垨涓鍚庣户缁�...");
 					continue;
 				}
 
 
-				// Bonder1、Bonder2、Fliper、VacuumBake、Aligner,统计G2和G1的数量, 配对组数, 多出的类型
+				// Bonder1銆丅onder2銆丗liper銆乂acuumBake銆丄ligner锛岀粺璁2鍜孏1鐨勬暟閲�, 閰嶅缁勬暟, 澶氬嚭鐨勭被鍨�
 				int nG2Count = 0, nG1Count = 0, nGlassGroup, nExtraType;
 				if (pBonder1->slotHasGlass(0)) {
 					nG2Count++;
@@ -691,7 +763,7 @@
 
 
 				// Measurement NG -> LoadPort
-				// NG回原位
+				// NG鍥炲師浣�
 				if (!rmd.armState[1]) {
 					m_pActiveRobotTask = createTransferTask_restore(pMeasurement, pLoadPorts);
 					CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
@@ -705,7 +777,7 @@
 				}
 
 				
-				// BakeCooling内部
+				// BakeCooling鍐呴儴
 				// Bake -> Cooling
 				if (!rmd.armState[0]) {
 					m_pActiveRobotTask = createTransferTask_bake_to_cooling(pBakeCooling);
@@ -819,9 +891,9 @@
 				continue;
 			}
 
-			// 批处理模式,最终以此为准,但先保留之前的单片模式
+			// 鎵瑰鐞嗘ā寮忥紝鏈�缁堜互姝や负鍑嗭紝浣嗗厛淇濈暀涔嬪墠鐨勫崟鐗囨ā寮�
 			else if (m_state == MASTERSTATE::RUNNING_BATCH) {
-				// 1) 控制作业生命周期保障
+				// 1) 鎺у埗浣滀笟鐢熷懡鍛ㄦ湡淇濋殰
 				if (m_pControlJob == nullptr) { unlock(); continue; }
 				CJState cjst = m_pControlJob->state();
 				if (cjst == CJState::Completed || cjst == CJState::Aborted || cjst == CJState::Failed) {
@@ -829,20 +901,20 @@
 					continue;
 				}
 				if (cjst == CJState::NoState) {
-					LOGI("<Master>ControlJob已经进入列队");
+					LOGI("<Master>ControlJob宸茬粡杩涘叆鍒楅槦");
 					m_pControlJob->queue();
 				}
 				if (m_pControlJob->state() == CJState::Queued) {
-					LOGI("<Master>ControlJob已经启动");
+					LOGI("<Master>ControlJob宸茬粡鍚姩");
 					m_pControlJob->start();
 					if (m_listener.onCjStart) m_listener.onCjStart(this, m_pControlJob);
 				}
 				if (m_pControlJob->state() == CJState::Paused) {
-					LOGI("<Master>ControlJob已经恢复运行");
+					LOGI("<Master>ControlJob宸茬粡鎭㈠杩愯");
 					m_pControlJob->resume();
 				}
 
-				// 2) 若当前无 PJ,则选择一个并上报
+				// 2) 鑻ュ綋鍓嶆棤 PJ锛屽垯閫夋嫨涓�涓苟涓婃姤
 				if (m_inProcesJobs.empty()) {
 					if (auto pj = acquireNextProcessJob()) {
 						m_inProcesJobs.push_back(pj);
@@ -850,35 +922,62 @@
 					}
 				}
 				if (m_inProcesJobs.empty()) {
-					LOGE("<Master>选择当前ProcessJob失败!");
+					LOGE("<Master>閫夋嫨褰撳墠ProcessJob澶辫触锛�");
 					unlock();
 					continue;
 				}
 
-				// 3) 若队列无 Glass,拉取到等待队列
+				// 3) 鑻ラ槦鍒楁棤 Glass锛屾媺鍙栧埌绛夊緟闃熷垪
 				if (m_queueGlasses.empty()) {
 					int nCount = acquireGlassToQueue();
 					if (nCount > 0) {
-						LOGI("<Master>已加入 %d 块Glass到工艺列队!", nCount);
+						LOGI("<Master>宸插姞鍏� %d 鍧桮lass鍒板伐鑹哄垪闃燂紒", nCount);
 					}
 				}
 
-				// 4) 机器人状态
+				// 4) 鏈哄櫒浜虹姸鎬�
 				RMDATA& rmd = pEFEM->getRobotMonitoringData();
 				if (rmd.status != ROBOT_STATUS::Idle && rmd.status != ROBOT_STATUS::Run) {
 					unlock(); continue;
 				}
 
-				// 5) 正在执行的 RobotTask 先让它跑完一拍
+				// 5) 姝e湪鎵ц鐨� RobotTask 鍏堣瀹冭窇瀹屼竴鎷�
 				if (m_pActiveRobotTask != nullptr) {
 					if (m_pActiveRobotTask->isPicked()) {
 						m_pActiveRobotTask->place();
 					}
-					unlock(); // 等当前任务完成或中止后继续
+					unlock(); // 绛夊綋鍓嶄换鍔″畬鎴愭垨涓鍚庣户缁�
 					continue;
 				}
 
-				// 6) ——关键:全局统计 G1/G2 与组数门限(与单片分支对齐)——
+				// 5.5) 鏆傚仠鐘舵�佹鏌ワ細鑻� CJ 鎴栧湪鍒� PJ 澶勪簬 Paused锛屾殏缂撹皟搴︽柊鐨勬惉閫�
+				bool pausedByEvent = false;
+				if (m_pControlJob != nullptr && m_pControlJob->state() == CJState::Paused) {
+					pausedByEvent = true;
+				}
+				for (auto pj : m_inProcesJobs) {
+					if (pj != nullptr && pj->state() == PJState::Paused) {
+						pausedByEvent = true;
+						break;
+					}
+				}
+				if (!pausedByEvent && m_bPauseAlarmRaised) {
+					if (m_pModelCtx != nullptr) {
+						m_pModelCtx->clearSoftAlarm(ALID_SOFTWARE_PAUSE_EVENT, 0, 0);
+					}
+					else {
+						AlarmManager& alarmManager = AlarmManager::getInstance();
+						alarmManager.clearAlarmByAttributes(ALID_SOFTWARE_PAUSE_EVENT, 0, 0, CToolUnits::getCurrentTimeString());
+					}
+					m_bPauseAlarmRaised = false;
+				}
+				if (pausedByEvent) {
+					LOGI("<Master>璋冨害鏆傚仠锛欳ontrolJob/ProcessJob 澶勪簬 Paused 鐘舵�侊紙鍙兘鐢� PauseEvent 瑙﹀彂锛�");
+					unlock();
+					continue;
+				}
+
+				// 6) 鈥斺�斿叧閿細鍏ㄥ眬缁熻 G1/G2 涓庣粍鏁伴棬闄愶紙涓庡崟鐗囧垎鏀榻愶級鈥斺��
 				auto countG1G2 = [&]() {
 					int g1 = 0, g2 = 0;
 					if (pBonder1->slotHasGlass(0)) g2++;
@@ -900,20 +999,20 @@
 				int nGlassGroup = min(g1Count, g2Count);
 				int nExtraType = (g1Count == g2Count ? 0 : (g1Count > g2Count ? 1 : 2));
 
-				// primary/secondary 统一定义(secondary 默认 G0)
+				// primary/secondary 缁熶竴瀹氫箟锛坰econdary 榛樿 G0锛�
 				MaterialsType primaryType = MaterialsType::G1;
 				MaterialsType secondaryType = MaterialsType::G0;
-				if (nExtraType == 0) primaryType = MaterialsType::G2; // 与单片分支一致
+				if (nExtraType == 0) primaryType = MaterialsType::G2; // 涓庡崟鐗囧垎鏀竴鑷�
 				else                 primaryType = MaterialsType::G1;
 
-				// 组数门限:≥2 组时不再从 LP 上片,避免堆积(与单片一致)
+				// 缁勬暟闂ㄩ檺锛氣墺2 缁勬椂涓嶅啀浠� LP 涓婄墖锛岄伩鍏嶅爢绉紙涓庡崟鐗囦竴鑷达級
 				bool blockLoadFromLP = (nGlassGroup >= 2);
 
-				// 7) Measurement -> LoadPort(固定:G1 优先回 LP)
+				// 7) Measurement -> LoadPort锛堝浐瀹氾細G1 浼樺厛鍥� LP锛�
 				if (rmd.armState[0] || rmd.armState[1]) {
 					LOGD("Arm1 %s, Arm2 %s.",
-						rmd.armState[0] ? _T("不可用") : _T("可用"),
-						rmd.armState[1] ? _T("不可用") : _T("可用"));
+						rmd.armState[0] ? _T("涓嶅彲鐢�") : _T("鍙敤"),
+						rmd.armState[1] ? _T("涓嶅彲鐢�") : _T("鍙敤"));
 				}
 				for (int s = 0; s < 4; s++) {
 					PortType pt = pLoadPorts[s]->getPortType();
@@ -928,7 +1027,7 @@
 				BATCH_PORT_PUT:
 				CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
 
-				// 8) Measurement NG -> LoadPort(原位回退)
+				// 8) Measurement NG -> LoadPort锛堝師浣嶅洖閫�锛�
 				if (!rmd.armState[1]) {
 					m_pActiveRobotTask = createTransferTask_restore(pMeasurement, pLoadPorts);
 					CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
@@ -940,7 +1039,7 @@
 					CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
 				}
 
-				// 10) BakeCooling 内部(Bake -> Cooling)
+				// 10) BakeCooling 鍐呴儴锛圔ake -> Cooling锛�
 				if (!rmd.armState[0]) {
 					m_pActiveRobotTask = createTransferTask_bake_to_cooling(pBakeCooling);
 					CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
@@ -956,7 +1055,7 @@
 					CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
 				}
 
-				// 12) Fliper(G2) -> Bonder(前置:VacuumBake 有 processed G1;输出 G2 到 Bonder slot0)
+				// 12) Fliper(G2) -> Bonder锛堝墠缃細VacuumBake 鏈� processed G1锛涜緭鍑� G2 鍒� Bonder slot0锛�
 				if (auto pSrcSlot = pVacuumBake->getProcessedSlot(MaterialsType::G1)) {
 					if (!rmd.armState[1] && pBonder1->canPlaceGlassInSlot(0)) {
 						m_pActiveRobotTask = createTransferTask(pFliper, pBonder1, MaterialsType::G2, MaterialsType::G0, 2);
@@ -968,7 +1067,7 @@
 					}
 				}
 
-				// 13) VacuumBake(G1) -> Bonder(槽级判定:slot0(G2) 已有且 slot1(G1) 为空)
+				// 13) VacuumBake(G1) -> Bonder锛堟Ы绾у垽瀹氾細slot0(G2) 宸叉湁涓� slot1(G1) 涓虹┖锛�
 				if (!rmd.armState[0] && pBonder1->slotHasGlass(0) && !pBonder1->slotHasGlass(1)) {
 					m_pActiveRobotTask = createTransferTask(pVacuumBake, pBonder1, MaterialsType::G1, MaterialsType::G0);
 					CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
@@ -978,7 +1077,7 @@
 					CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
 				}
 
-				// 14) Aligner -> Fliper(G2) 以及 -> VacuumBake(G1)(固定映射)
+				// 14) Aligner -> Fliper(G2) 浠ュ強 -> VacuumBake(G1)锛堝浐瀹氭槧灏勶級
 				if (!rmd.armState[1]) {
 					m_pActiveRobotTask = createTransferTask(pAligner, pFliper, MaterialsType::G2, MaterialsType::G0);
 					CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
@@ -988,13 +1087,13 @@
 					CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
 				}
 
-				// 15) Aligner -> LoadPort(restore)
+				// 15) Aligner -> LoadPort锛坮estore锛�
 				if (!rmd.armState[1]) {
 					m_pActiveRobotTask = createTransferTask_restore(pAligner, pLoadPorts);
 					CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
 				}
 
-				// 16) LoadPort -> Aligner(受组数门限控制;统一 buddy/状态时序)
+				// 16) LoadPort -> Aligner锛堝彈缁勬暟闂ㄩ檺鎺у埗锛涚粺涓� buddy/鐘舵�佹椂搴忥級
 				if (blockLoadFromLP) { unlock(); continue; }
 
 				for (int s = 0; s < 4; s++) {
@@ -1011,17 +1110,17 @@
 								continue;
 							}
 
-							// 统一:queue -> start -> setContext -> move queue→inProcess -> onPanelStart
+							// 缁熶竴锛歲ueue -> start -> setContext -> move queue鈫抜nProcess -> onPanelStart
 							pGlass->queue();
 							pGlass->start();
 							pEFEM->setContext(pGlass);
 
 							bool bMoved = glassFromQueueToInPorcess(pGlass);
 							if (bMoved) {
-								LOGI("<Master>Glass(%s)从等待列队到工艺列队转移成功.", pGlass->getID().c_str());
+								LOGI("<Master>Glass(%s)浠庣瓑寰呭垪闃熷埌宸ヨ壓鍒楅槦杞Щ鎴愬姛.", pGlass->getID().c_str());
 							}
 							else {
-								LOGE("<Master>Glass(%s)从等待列队到工艺列队转移失败.", pGlass->getID().c_str());
+								LOGE("<Master>Glass(%s)浠庣瓑寰呭垪闃熷埌宸ヨ壓鍒楅槦杞Щ澶辫触.", pGlass->getID().c_str());
 							}
 
 							if (m_listener.onPanelStart) m_listener.onPanelStart(this, pGlass);
@@ -1038,9 +1137,9 @@
 			}
 
 
-			// 千传模式调度逻辑
+			// 鍗冧紶妯″紡璋冨害閫昏緫
 			else if (m_state == MASTERSTATE::RUNNING_CONTINUOUS_TRANSFER) {
-				// 检测判断robot状态
+				// 妫�娴嬪垽鏂璻obot鐘舵��
 				RMDATA& rmd = pEFEM->getRobotMonitoringData();
 				if (rmd.status != ROBOT_STATUS::Idle && rmd.status != ROBOT_STATUS::Run) {
 					unlock();
@@ -1052,8 +1151,8 @@
 						m_pActiveRobotTask->place();
 					}
 					unlock();
-					// 检测到当前有正在下午的任务,确保当前任务完成或中止后继续
-					// LOGI("检测到当前有正在下午的任务,确保当前任务完成或中止后继续...");
+					// 妫�娴嬪埌褰撳墠鏈夋鍦ㄤ笅鍗堢殑浠诲姟锛岀‘淇濆綋鍓嶄换鍔″畬鎴愭垨涓鍚庣户缁�
+					// LOGI("妫�娴嬪埌褰撳墠鏈夋鍦ㄤ笅鍗堢殑浠诲姟锛岀‘淇濆綋鍓嶄换鍔″畬鎴愭垨涓鍚庣户缁�...");
 					continue;
 				}
 
@@ -1089,20 +1188,20 @@
 						3, pMeasurement, 0);
 					if (m_pActiveRobotTask != nullptr) {
 						m_nContinuousTransferStep = CTStep_BakeCooling_Measurement;
-						LOGI("<ContinuousTransfer>千传测试,开始搬送任务(BakeCooling -> Measurement)...");
+						LOGI("<ContinuousTransfer>鍗冧紶娴嬭瘯锛屽紑濮嬫惉閫佷换鍔�(BakeCooling -> Measurement)...");
 					}
 					CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
 				}
 
 				
-				// BakeCooling内部
+				// BakeCooling鍐呴儴
 				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)...");
+						LOGI("<ContinuousTransfer>鍗冧紶娴嬭瘯锛屽紑濮嬫惉閫佷换鍔�(BakeCooling-2 -> BakeCooling-3)...");
 					}
 					CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
 				}
@@ -1112,7 +1211,7 @@
 						1, pBakeCooling, 2);
 					if (m_pActiveRobotTask != nullptr) {
 						m_nContinuousTransferStep = CTStep_BakeCooling_BakeCooling2;
-						LOGI("<ContinuousTransfer>千传测试,开始搬送任务(BakeCooling-1 -> BakeCooling-2)...");
+						LOGI("<ContinuousTransfer>鍗冧紶娴嬭瘯锛屽紑濮嬫惉閫佷换鍔�(BakeCooling-1 -> BakeCooling-2)...");
 					}
 					CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
 				}
@@ -1122,7 +1221,7 @@
 						0, pBakeCooling, 1);
 					if (m_pActiveRobotTask != nullptr) {
 						m_nContinuousTransferStep = CTStep_BakeCooling_BakeCooling1;
-						LOGI("<ContinuousTransfer>千传测试,开始搬送任务(BakeCooling-0 -> BakeCooling-1)...");
+						LOGI("<ContinuousTransfer>鍗冧紶娴嬭瘯锛屽紑濮嬫惉閫佷换鍔�(BakeCooling-0 -> BakeCooling-1)...");
 					}
 					CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
 				}
@@ -1134,7 +1233,7 @@
 						1, pBakeCooling, 0);
 					if (m_pActiveRobotTask != nullptr) {
 						m_nContinuousTransferStep = CTStep_VacuumBake_BakeCooling;
-						LOGI("<ContinuousTransfer>千传测试,开始搬送任务(VacuumBake(G1) -> BakeCooling)...");
+						LOGI("<ContinuousTransfer>鍗冧紶娴嬭瘯锛屽紑濮嬫惉閫佷换鍔�(VacuumBake(G1) -> BakeCooling)...");
 					}
 					CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
 				}
@@ -1146,7 +1245,7 @@
 						0, pVacuumBake, 1);
 					if (m_pActiveRobotTask != nullptr) {
 						m_nContinuousTransferStep = CTStep_VacuumBake_VacuumBake;
-						LOGI("<ContinuousTransfer>千传测试,开始搬送任务(VacuumBake(G1-0) -> VacuumBake(G1-1))...");
+						LOGI("<ContinuousTransfer>鍗冧紶娴嬭瘯锛屽紑濮嬫惉閫佷换鍔�(VacuumBake(G1-0) -> VacuumBake(G1-1))...");
 					}
 					CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
 				}
@@ -1158,7 +1257,7 @@
 						1, pVacuumBake, 0);
 					if (m_pActiveRobotTask != nullptr) {
 						m_nContinuousTransferStep = CTStep_Bonder2_VacuumBake;
-						LOGI("<ContinuousTransfer>千传测试,开始搬送任务(Bonder2 -> VacuumBake(G1))...");
+						LOGI("<ContinuousTransfer>鍗冧紶娴嬭瘯锛屽紑濮嬫惉閫佷换鍔�(Bonder2 -> VacuumBake(G1))...");
 					}
 					CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
 				}
@@ -1170,7 +1269,7 @@
 						1, pBonder2, 1);
 					if (m_pActiveRobotTask != nullptr) {
 						m_nContinuousTransferStep = CTStep_Bonder1_Bonder2;
-						LOGI("<ContinuousTransfer>千传测试,开始搬送任务(Bonder1 -> Bonder2)...");
+						LOGI("<ContinuousTransfer>鍗冧紶娴嬭瘯锛屽紑濮嬫惉閫佷换鍔�(Bonder1 -> Bonder2)...");
 					}
 					CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
 				}
@@ -1182,7 +1281,7 @@
 						0, pBonder1, 1);
 					if (m_pActiveRobotTask != nullptr) {
 						m_nContinuousTransferStep = CTStep_Fliper_Bonder1;
-						LOGI("<ContinuousTransfer>千传测试,开始搬送任务(Fliper(G2) -> Bonder1)...");
+						LOGI("<ContinuousTransfer>鍗冧紶娴嬭瘯锛屽紑濮嬫惉閫佷换鍔�(Fliper(G2) -> Bonder1)...");
 					}
 					CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
 				}
@@ -1194,7 +1293,7 @@
 						0, pFliper, 0);
 					if (m_pActiveRobotTask != nullptr) {
 						m_nContinuousTransferStep = CTStep_Aligner_Fliper;
-						LOGI("<ContinuousTransfer>千传测试,开始搬送任务(Aligner -> Fliper(G2))...");
+						LOGI("<ContinuousTransfer>鍗冧紶娴嬭瘯锛屽紑濮嬫惉閫佷换鍔�(Aligner -> Fliper(G2))...");
 					}
 					CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
 				}
@@ -1213,7 +1312,7 @@
 								m_nContinuousTransferStep = CTStep_LoadPort_Aligner;
 								m_nContinuousWorkingPort = p;
 								m_nContinuousWorkingSlot = slot;
-								LOGI("<ContinuousTransfer>千传测试,开始搬送任务(LoadPort -> Aligner)...");
+								LOGI("<ContinuousTransfer>鍗冧紶娴嬭瘯锛屽紑濮嬫惉閫佷换鍔�(LoadPort -> Aligner)...");
 								pEFEM->setContext(m_pActiveRobotTask->getContext());
 								goto CT_PORT_GET;
 							}
@@ -1224,7 +1323,7 @@
 			CT_PORT_GET:
 				if (m_pActiveRobotTask != nullptr) {
 					m_nContinuousTransferStep = CTStep_begin;
-					LOGI("<ContinuousTransfer>千传测试,开始第 %d 轮", m_nContinuousTransferCount + 1);
+					LOGI("<ContinuousTransfer>鍗冧紶娴嬭瘯锛屽紑濮嬬 %d 杞�", m_nContinuousTransferCount + 1);
 				}
 				CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
 
@@ -1241,13 +1340,13 @@
 
 
 		// _endthreadex(0);
-		TRACE("CMaster::DispatchProc 线程退出\n");
+		TRACE("CMaster::DispatchProc 绾跨▼閫�鍑篭n");
 		return 0;
 	}
 
 	unsigned CMaster::ReadBitsProc()
 	{
-		// 标志位清0复位		
+		// 鏍囧織浣嶆竻0澶嶄綅		
 		{
 			StationIdentifier station;
 			station.nNetNo = 0;
@@ -1258,13 +1357,13 @@
 
 
 		while (1) {
-			// 待退出信号或时间到
+			// 寰呴��鍑轰俊鍙锋垨鏃堕棿鍒�
 			int nRet = ::WaitForSingleObject(m_hEventReadBitsThreadExit[0], 1000);
 			if (nRet == WAIT_OBJECT_0) {
 				break;
 			}
 
-			// 读标志位
+			// 璇绘爣蹇椾綅
 			for (auto item : m_listEquipment) {
 				const StationIdentifier& station = item->getStation();
 				MemoryBlock& block = item->getReadBitBlock();
@@ -1282,7 +1381,7 @@
 
 
 		// _endthreadex(0);
-		TRACE("CMaster::ReadBitsProc 线程退出\n");
+		TRACE("CMaster::ReadBitsProc 绾跨▼閫�鍑篭n");
 		return 0;
 	}
 
@@ -1317,10 +1416,10 @@
 		listener.onPreFethedOutJob = [&](void* pEquipment, int port, CJobDataB* pJobDataB) -> BOOL {
 			CEquipment* p = (CEquipment*)pEquipment;
 
-			// 可能要加这一句
+			// 鍙兘瑕佸姞杩欎竴鍙�
 			Sleep(750);
 
-			// 取片,更新当前搬送任务
+			// 鍙栫墖锛屾洿鏂板綋鍓嶆惉閫佷换鍔�
 			BOOL bOk = FALSE;
 			lock();
 			if (m_pActiveRobotTask != nullptr) {
@@ -1335,7 +1434,7 @@
 							&& pJobDataS->getCassetteSequenceNo() == pJobDataB->getCassetteSequenceNo()
 							&& pJobDataS->getJobSequenceNo() == pJobDataB->getJobSequenceNo()) {
 							bOk = TRUE;
-							LOGD("<CMaster>onPreFethedOutJob, 已校验数据一致性.");
+							LOGD("<CMaster>onPreFethedOutJob, 宸叉牎楠屾暟鎹竴鑷存��.");
 						}
 						LOGD("<CMaster>onPreFethedOutJob 0004.");
 						if (pJobDataS != nullptr) {
@@ -1355,7 +1454,7 @@
 			unlock();
 
 			if (!bOk) {
-				LOGE("<CMaster>onPreFethedOutJob, 数据校验失败.");
+				LOGE("<CMaster>onPreFethedOutJob, 鏁版嵁鏍¢獙澶辫触.");
 			}
 
 			return bOk;
@@ -1364,14 +1463,14 @@
 		listener.onPreStoredJob = [&](void* pEquipment, int port, CJobDataB* pJobDataB, short& slot) -> BOOL {
 			CEquipment* p = (CEquipment*)pEquipment;
 
-			// 可能要加这一句
+			// 鍙兘瑕佸姞杩欎竴鍙�
 			Sleep(750);
 
-			// 放片,更新当前搬送任务
+			// 鏀剧墖锛屾洿鏂板綋鍓嶆惉閫佷换鍔�
 			BOOL bOk = FALSE;
 			lock();
 			if (m_pActiveRobotTask != nullptr) {
-				// 是否已经进入手臂(即取片完成),进入下一步,放片
+				// 鏄惁宸茬粡杩涘叆鎵嬭噦(鍗冲彇鐗囧畬鎴�),杩涘叆涓嬩竴姝ワ紝鏀剧墖
 				if (m_pActiveRobotTask->isPicking() && 
 					((m_pActiveRobotTask->getArmNo() == 1 && p->getID() == EQ_ID_ARM_TRAY1)
 					|| (m_pActiveRobotTask->getArmNo() == 2 && p->getID() == EQ_ID_ARM_TRAY2))
@@ -1380,32 +1479,32 @@
 					bOk = TRUE;
 				}
 
-				// 是否放片完成
+				// 鏄惁鏀剧墖瀹屾垚
 				else if (m_pActiveRobotTask->isPlacing() &&
 					m_pActiveRobotTask->getTarPosition() == p->getID()) {
 					CGlass* pGlass = p->getGlassFromSlot(m_pActiveRobotTask->getTarSlot());
 					if (pGlass == nullptr) {
 						bOk = TRUE;
 						slot = m_pActiveRobotTask->getTarSlot();
-						LOGI("<CMaster>onPreStoredJob, 已校验数据一致性.");
+						LOGI("<CMaster>onPreStoredJob, 宸叉牎楠屾暟鎹竴鑷存��.");
 					}
 				}
 
-				// 是否回撤
+				// 鏄惁鍥炴挙
 				else if (m_pActiveRobotTask->isRestoring() &&
 					m_pActiveRobotTask->getSrcPosition() == p->getID()) {
 					CGlass* pGlass = p->getGlassFromSlot(m_pActiveRobotTask->getSrcSlot());
 					if (pGlass == nullptr && m_pActiveRobotTask->getSrcSlot() == port) {
 						bOk = TRUE;
 						slot = m_pActiveRobotTask->getSrcSlot();
-						LOGI("<CMaster>onPreStoredJob, 已校验数据一致性.");
+						LOGI("<CMaster>onPreStoredJob, 宸叉牎楠屾暟鎹竴鑷存��.");
 					}
 				}
 			}
 			unlock();
 
 			if (!bOk) {
-				LOGE("<CMaster>onPreStoredJob, 数据校验失败.");
+				LOGE("<CMaster>onPreStoredJob, 鏁版嵁鏍¢獙澶辫触.");
 			}
 
 			return bOk;
@@ -1418,11 +1517,11 @@
 				m_listener.onEqDataChanged(this, p, 0);
 			}
 
-			// 取放片,更新当前搬送任务
+			// 鍙栨斁鐗囷紝鏇存柊褰撳墠鎼�佷换鍔�
 			if (code == EDCC_FETCHOUT_JOB) {
 				lock();
 				if (m_pActiveRobotTask != nullptr && m_pActiveRobotTask->getSrcPosition() == p->getID()) {
-					LOGI("开始取片...");
+					LOGI("寮�濮嬪彇鐗�...");
 				}
 				unlock();
 			}
@@ -1433,7 +1532,7 @@
 					&& ((m_pActiveRobotTask->getArmNo() == 1 && p->getID() == EQ_ID_ARM_TRAY1)
 						|| (m_pActiveRobotTask->getArmNo() == 2 && p->getID() == EQ_ID_ARM_TRAY2))
 					) {
-					LOGI("取片完成.");
+					LOGI("鍙栫墖瀹屾垚.");
 					m_pActiveRobotTask->fetchOut();
 					m_pActiveRobotTask->picked();
 				}
@@ -1447,17 +1546,17 @@
 					if (m_state == MASTERSTATE::RUNNING_CONTINUOUS_TRANSFER) {
 						if (m_nContinuousTransferStep == CTStep_end) {
 							m_nContinuousTransferCount++;
-							LOGI("<ContinuousTransfer>千传测试,第 %d 轮结束", m_nContinuousTransferCount);
+							LOGI("<ContinuousTransfer>鍗冧紶娴嬭瘯锛岀 %d 杞粨鏉�", m_nContinuousTransferCount);
 							if (m_listener.onCTRoundEnd != nullptr) {
 								m_listener.onCTRoundEnd(this, m_nContinuousTransferCount);
 							}
 						}
 					}
 
-					LOGI("放片完成...");
-					// 完成此条搬送任务,但要把数据和消息上抛应用层
+					LOGI("鏀剧墖瀹屾垚...");
+					// 瀹屾垚姝ゆ潯鎼�佷换鍔★紝浣嗚鎶婃暟鎹拰娑堟伅涓婃姏搴旂敤灞�
 
-					// 如果是搬送回从AOI搬送回Port, 则glass工艺完成
+					// 濡傛灉鏄惉閫佸洖浠嶢OI鎼�佸洖Port, 鍒檊lass宸ヨ壓瀹屾垚
 					if (m_pActiveRobotTask->getSrcPosition() == EQ_ID_MEASUREMENT) {
 						CGlass* pGlass = (CGlass*)m_pActiveRobotTask->getContext();
 						pGlass->complete();
@@ -1466,32 +1565,33 @@
 						this->saveState();
 						bool bMoved = glassFromInPorcessToComplete(pGlass);
 						if (bMoved) {
-							LOGI("<Master>Glass(%s)从工艺列队到完成列队转移成功.",
+							LOGI("<Master>Glass(%s)浠庡伐鑹哄垪闃熷埌瀹屾垚鍒楅槦杞Щ鎴愬姛.",
 								pGlass->getID().c_str());
 						}
 						else {
-							LOGE("<Master>Glass(%s)从工艺列队到完成列队转移失败.",
+							LOGE("<Master>Glass(%s)浠庡伐鑹哄垪闃熷埌瀹屾垚鍒楅槦杞Щ澶辫触.",
 								pGlass->getID().c_str());
 						}
 						if (m_listener.onPanelEnd != nullptr) {
 							m_listener.onPanelEnd(this, pGlass);
 						}
 
-						// 检查PJ是否已经完成
+						// 妫�鏌J鏄惁宸茬粡瀹屾垚
 						CProcessJob* pJob = getGlassProcessJob((CGlass*)m_pActiveRobotTask->getContext());
 						if (pJob != nullptr && checkAndUpdatePjComplete(pJob)) {
 							this->saveState();
-							LOGE("<Master>ProcessJob(%s)完成.",
+							LOGE("<Master>ProcessJob(%s)瀹屾垚.",
 								pJob->id().c_str());
+							processJobFromInPorcessToComplete(pJob);
 							if (m_listener.onPjEnd != nullptr) {
 								m_listener.onPjEnd(this, pJob);
 							}
 
-							// 检查CJ是否已经完成
+							// 妫�鏌J鏄惁宸茬粡瀹屾垚
 							ASSERT(m_pControlJob);
 							if (checkAndUpdateCjComplete(m_pControlJob)) {
 								this->saveState();
-								LOGE("<Master>ControlJob(%s)完成.",
+								LOGE("<Master>ControlJob(%s)瀹屾垚.",
 									m_pControlJob->id().c_str());
 								if (m_listener.onCjEnd != nullptr) {
 									m_listener.onCjEnd(this, pJob);
@@ -1520,8 +1620,8 @@
 					&& m_pActiveRobotTask->getSrcPosition() == p->getID()) {
 					m_pActiveRobotTask->stored();
 					m_pActiveRobotTask->restored();
-					LOGI("回撤完成...");
-					// 完成此条搬送任务,但要把数据和消息上抛应用层
+					LOGI("鍥炴挙瀹屾垚...");
+					// 瀹屾垚姝ゆ潯鎼�佷换鍔★紝浣嗚鎶婃暟鎹拰娑堟伅涓婃姏搴旂敤灞�
 					unlock();
 
 
@@ -1536,27 +1636,38 @@
 				unlock();
 			}
 		};
-		listener.onProcessStateChanged = [&](void* pEquipment, int slotNo, PROCESS_STATE state) -> void {
+		listener.onProcessStateChanged = [&](void* pEquipment, int slotNo, PROCESS_STATE prevState, PROCESS_STATE state) -> void {
 			ASSERT(1 <= slotNo && slotNo <= 8);
 			int eqid = ((CEquipment*)pEquipment)->getID();
 			CGlass* pGlass = ((CEquipment*)pEquipment)->getGlassFromSlot(slotNo);
 			LOGI("<Master>onProcessStateChanged<%d>", (int)state);
 			if (state == PROCESS_STATE::Processing) {
 				if (pGlass != nullptr) {
-					m_pCollector->batchStart(eqid,
+					m_pCollector->batchStart(SlotToMid(eqid, slotNo),
 						pGlass->getID().c_str(), 10 * 60 * 1000ULL);
 				}
 			}
 			else if (state == PROCESS_STATE::Complete) {
-				m_pCollector->batchStop(eqid);
+				if (pGlass != nullptr) {
+					m_pCollector->batchStop(SlotToMid(eqid, slotNo));
+				}
+			}
+
+			if (m_listener.onProcessStateChanged != nullptr) {
+				m_listener.onProcessStateChanged(this, (CEquipment*)pEquipment, slotNo, prevState, state);
+			}
+		};
+		listener.onProcessDataReport = [&](void* pEquipment, const std::vector<CParam>& params) {
+			if (m_listener.onProcessDataReport != nullptr) {
+				m_listener.onProcessDataReport(this, (CEquipment*)pEquipment, params);
 			}
 		};
 		listener.onMapMismatch = [&](void* pEquipment, short scanMap, short downMap) {
-			LOGE("<Master-%s>Port InUse, map(%d!=%d)不一致,请检查。",
+			LOGE("<Master-%s>Port InUse, map(%d!=%d)涓嶄竴鑷达紝璇锋鏌ャ��",
 				((CEquipment*)pEquipment)->getName().c_str(), scanMap, downMap);
 		};
 		listener.onPortStatusChanged = [&](void* pEquipment, short status, __int64 data) {
-			LOGE("<Master-%s>onPortStatusChanged。status=%d, data=%lld", ((CEquipment*)pEquipment)->getName().c_str(), status);
+			LOGE("<Master-%s>onPortStatusChanged銆俿tatus=%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();
@@ -1622,86 +1733,161 @@
 			}
 		};
 		listener.onSVDataReport = [&](void* pEquipment, void* pData) {
+			const bool allowSvLog =
+				(m_state == MASTERSTATE::RUNNING ||
+					m_state == MASTERSTATE::RUNNING_CONTINUOUS_TRANSFER ||
+					m_state == MASTERSTATE::RUNNING_BATCH ||
+					m_state == MASTERSTATE::STARTING);
+			const bool allowCurve = allowSvLog || (m_curveMode == CurveMode::EmptyChamber);
+			if (!allowCurve) {
+				return;
+			}
 			CSVData* pSVData = (CSVData*)pData;
 			auto rawData = pSVData->getSVRawData();
 			std::vector<CParam> params;
 			((CEquipment*)pEquipment)->parsingSVData((const char*)rawData.data(), rawData.size(), params);
 		
 
-			// 以下加入到曲线数据中
-
+			// 浠ヤ笅鍔犲叆鍒版洸绾挎暟鎹腑
+			LOGD("<Master>onSVDataReport 001");
 
 			const int64_t ts = now_ms_epoch();
 			int eqid = ((CEquipment*)pEquipment)->getID();
 			if (eqid == EQ_ID_Bonder1 || eqid == EQ_ID_Bonder2) {
-				// 定义 Bonder 的特定映射
+				LOGD("<Master>onSVDataReport 002A");
+				// 瀹氫箟 Bonder 鐨勭壒瀹氭槧灏�
 				std::vector<std::pair<int, int>> bonderMapping = {
 					{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}, {7, 7},
 					{8, 8}, {9, 9}, {10, 10}, {11, 11}, {12, 12}, {13, 13}, {14, 14}, {15, 15}, {16, 16}
 				};
 
-				CGlass* pGlass = ((CEquipment*)pEquipment)->getGlassFromSlot(0);
+				CGlass* pGlass = ((CEquipment*)pEquipment)->getGlassFromSlot(2);
 				auto& dataTypes = CServoUtilsTool::getEqDataTypes();
-				auto& bonderTypes = dataTypes[eqid];
+				auto& bonderTypes = dataTypes[SlotToMid(eqid, 2)];
 				for (const auto& mapping : bonderMapping) {
 					int paramIndex = mapping.first;
 					int channel = mapping.second;
 
 					if (paramIndex < params.size() && channel - 1 < bonderTypes.size()) {
 						if(m_pCollector != nullptr)
-							m_pCollector->buffersPush(eqid, channel, ts, params.at(paramIndex).getDoubleValue());
+							m_pCollector->buffersPush(SlotToMid(eqid, 2), channel, ts, params.at(paramIndex).getDoubleValue());
 						if(pGlass != nullptr)
-							pGlass->addSVData(eqid, bonderTypes[channel], ts, params.at(paramIndex).getDoubleValue());
+							pGlass->addSVData(eqid, bonderTypes[channel - 1], ts, params.at(paramIndex).getDoubleValue());
 					}
 				}
 			}
 			else if (eqid == EQ_ID_VACUUMBAKE) {
-				// 定义 VACUUMBAKE 的特定映射
+				LOGD("<Master>onSVDataReport 002");
+				// 瀹氫箟 VACUUMBAKE 鐨勭壒瀹氭槧灏�
 				std::vector<std::pair<int, int>> vacuumMapping = {
 					{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}, {7, 7},
 					{10, 8}, {11, 9}, {12, 10}, {13, 11}, {14, 12}, {15, 13}, {16, 14}
 				};
 
-				CGlass* pGlass = ((CEquipment*)pEquipment)->getGlassFromSlot(0);
+				CGlass* pGlass1 = ((CEquipment*)pEquipment)->getGlassFromSlot(1);
+				CGlass* pGlass2 = ((CEquipment*)pEquipment)->getGlassFromSlot(2);
 				auto& dataTypes = CServoUtilsTool::getEqDataTypes();
-				auto& vacuumbakeTypes = dataTypes[eqid];
+				auto& vacuumbakeTypes = dataTypes[SlotToMid(eqid, 1)];
+				LOGD("<Master>onSVDataReport 003 : %d", vacuumMapping.size());
 				for (const auto& mapping : vacuumMapping) {
 					int paramIndex = mapping.first;
 					int channel = mapping.second;
 
-					if (paramIndex < params.size() && channel - 1 < vacuumbakeTypes.size()) {
-						if (m_pCollector != nullptr)
-							m_pCollector->buffersPush(eqid, channel, ts, params.at(paramIndex).getDoubleValue());
-						if (pGlass != nullptr)
-							pGlass->addSVData(eqid, vacuumbakeTypes[channel], ts, params.at(paramIndex).getDoubleValue());
+					if (paramIndex < params.size()) {
+						auto& param = params.at(paramIndex);
+						double value = param.getDoubleValue();
+						const std::string& paramName = param.getName();
+						const char slotTag = !paramName.empty() ? paramName[0] : '\0';
+						const int typeIndex = (slotTag == 'B') ? (channel - 8) : (channel - 1);
+						if (typeIndex < 0 || typeIndex >= (int)vacuumbakeTypes.size()) {
+							continue;
+						}
+						const int pushChannel = typeIndex + 1;
+						const std::string& dataType = vacuumbakeTypes[typeIndex];
+
+						if (m_pCollector != nullptr) {
+							if (slotTag == 'A')
+								m_pCollector->buffersPush(SlotToMid(eqid, 1), pushChannel, ts, value);
+							else if (slotTag == 'B')
+								m_pCollector->buffersPush(SlotToMid(eqid, 2), pushChannel, ts, value);
+						}
+
+						// 鏍规嵁鑵斾綋鍓嶇紑鍐欏叆瀵瑰簲 Slot 鐨勭幓鐠�
+						if (pGlass1 != nullptr && !dataType.empty() && slotTag == 'A')
+							pGlass1->addSVData(eqid, dataType, ts, value);
+						if (pGlass2 != nullptr && !dataType.empty() && slotTag == 'B')
+							pGlass2->addSVData(eqid, dataType, ts, value);
 					}
 				}
 			}
 			else if (eqid == EQ_ID_BAKE_COOLING) {
-				// 定义 BAKE_COOLING 的特定映射
+				LOGD("<Master>onSVDataReport 002B");
+				// 瀹氫箟 BAKE_COOLING 鐨勭壒瀹氭槧灏�
 				std::vector<std::pair<int, int>> coolingMapping = {
 					{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6},
 					{11, 7}, {12, 8}, {13, 9}, {14, 10}, {15, 11}, {16, 12}
 				};
 
-				CGlass* pGlass = ((CEquipment*)pEquipment)->getGlassFromSlot(0);
+				CGlass* pGlass1 = ((CEquipment*)pEquipment)->getGlassFromSlot(1); // A Bake
+				CGlass* pGlass2 = ((CEquipment*)pEquipment)->getGlassFromSlot(2); // A Cool
+				CGlass* pGlass3 = ((CEquipment*)pEquipment)->getGlassFromSlot(3); // B Bake
+				CGlass* pGlass4 = ((CEquipment*)pEquipment)->getGlassFromSlot(4); // B Cool
 				auto& dataTypes = CServoUtilsTool::getEqDataTypes();
-				auto& coolingTypes = dataTypes[eqid];
+				auto& coolingTypes = dataTypes[SlotToMid(eqid, 1)];
+				LOGD("<Master>onSVDataReport 003B : %d", coolingMapping.size());
+				auto addToGlass = [&](CGlass* glass, const std::string& type, double val) {
+					if (glass != nullptr)
+						glass->addSVData(eqid, type, ts, val);
+				};
 				for (const auto& mapping : coolingMapping) {
 					int paramIndex = mapping.first;
 					int channel = mapping.second;
 
-					if (paramIndex < params.size() && channel - 1 < coolingTypes.size()) {
-						if (m_pCollector != nullptr)
-							m_pCollector->buffersPush(eqid, channel, ts, params.at(paramIndex).getDoubleValue());
-						if (pGlass != nullptr)
-							pGlass->addSVData(eqid, coolingTypes[channel], ts, params.at(paramIndex).getDoubleValue());
+					if (paramIndex < params.size()) {
+						auto& param = params.at(paramIndex);
+						double value = param.getDoubleValue();
+						const std::string& paramName = param.getName();
+						const char slotTag = !paramName.empty() ? paramName[0] : '\0';
+						const bool paramIsBake = paramName.find("鐑樼儰") != std::string::npos;
+						const bool paramIsCooling = paramName.find("鍐峰嵈") != std::string::npos;
+						const int typeIndex = (slotTag == 'B') ? (channel - 7) : (channel - 1);
+						if (typeIndex < 0 || typeIndex >= (int)coolingTypes.size()) {
+							continue;
+						}
+						const int pushChannel = typeIndex + 1;
+						const std::string& dataType = coolingTypes[typeIndex];
+
+						if (m_pCollector != nullptr && paramIsBake) {
+							if (slotTag == 'A')
+								m_pCollector->buffersPush(SlotToMid(eqid, 1), pushChannel, ts, value);
+							else if (slotTag == 'B')
+								m_pCollector->buffersPush(SlotToMid(eqid, 3), pushChannel, ts, value);
+						}
+
+						if (!dataType.empty()) {
+							switch (slotTag) {
+							case 'A':
+								if (paramIsBake)
+									addToGlass(pGlass1, dataType, value);
+								else if (paramIsCooling)
+									addToGlass(pGlass2, dataType, value);
+								break;
+							case 'B':
+								if (paramIsBake)
+									addToGlass(pGlass3, dataType, value);
+								else if (paramIsCooling)
+									addToGlass(pGlass4, dataType, value);
+								break;
+							default:
+								break;
+							}
+						}
 					}
 				}
 			}
 
 
-			// 以下是输出测试
+			// 浠ヤ笅鏄緭鍑烘祴璇�
 			std::string strOut;
 			char szBuffer[256];
 			for (auto p : params) {
@@ -1715,6 +1901,10 @@
 				strOut.append(szBuffer);
 			}
 			LOGD("<CMaster-%s>SVDataReport:%s", ((CEquipment*)pEquipment)->getName().c_str(), strOut.c_str());
+
+			if (m_listener.onSVDataReport != nullptr) {
+				m_listener.onSVDataReport(this, (CEquipment*)pEquipment, params);
+			}
 		};
 		listener.onPanelDataReport = [&](void* pEquipment, void* pContext) {
 			LOGD("<CMaster-%s>onPanelDataReport", ((CEquipment*)pEquipment)->getName().c_str());
@@ -1722,18 +1912,28 @@
 			CEquipment* pEq = (CEquipment*)pEquipment;
 			CGlass* pGlass = (CGlass*)pContext;
 
-			// 如果AOI检测失败,要停机
+			// 濡傛灉AOI妫�娴嬪け璐ワ紝瑕佸仠鏈�
 			if (pEq->getID() == EQ_ID_MEASUREMENT) {
 				LOGD("<CMaster-%s>onPanelDataReport 01", ((CEquipment*)pEquipment)->getName().c_str());
 				if (pGlass->getAOIInspResult() == InspResult::Fail) {
 					LOGD("<CMaster-%s>onPanelDataReport 02", ((CEquipment*)pEquipment)->getName().c_str());
 					if (stop() == 0) {
 						m_nLastError = ER_CODE_AOI_NG;
-						m_strLastError = "AOI检测未通过.";
+						m_strLastError = "AOI妫�娴嬫湭閫氳繃.";
 					}
 				}
 			}
 
+		};
+		listener.onReceivedJob = [&](void* pEquipment, int port, CJobDataS* pJobDataS) {
+			if (m_listener.onJobReceived != nullptr) {
+				m_listener.onJobReceived(this, (CEquipment*)pEquipment, port, pJobDataS);
+			}
+		};
+		listener.onSentOutJob = [&](void* pEquipment, int port, CJobDataS* pJobDataS) {
+			if (m_listener.onJobSentOut != nullptr) {
+				m_listener.onJobSentOut(this, (CEquipment*)pEquipment, port, pJobDataS);
+			}
 		};
 		pEquipment->setListener(listener);
 		pEquipment->setCcLink(&m_cclink);
@@ -1766,7 +1966,7 @@
 	}
 
 	/*
-	 * 添加LoadPort1
+	 * 娣诲姞LoadPort1
 	 * index -- 0~3
 	 */
 	CLoadPort* CMaster::addLoadPort(int index)
@@ -1786,7 +1986,7 @@
 
 
 		pEquipment->init();
-		LOGE("已添加“%s”.", pEquipment->getName().c_str());
+		LOGE("宸叉坊鍔犫��%s鈥�.", pEquipment->getName().c_str());
 
 
 		return pEquipment;
@@ -1805,7 +2005,7 @@
 
 
 		pEquipment->init();
-		LOGE("已添加“Fliper”.");
+		LOGE("宸叉坊鍔犫�淔liper鈥�.");
 		return pEquipment;
 	}
 
@@ -1822,7 +2022,7 @@
 
 
 		pEquipment->init();
-		LOGE("已添加“VacuumBake”.");
+		LOGE("宸叉坊鍔犫�淰acuumBake鈥�.");
 
 		return pEquipment;
 	}
@@ -1840,7 +2040,7 @@
 
 
 		pEquipment->init();
-		LOGE("已添加“Aligner”.");
+		LOGE("宸叉坊鍔犫�淎ligner鈥�.");
 
 		return pEquipment;
 	}
@@ -1858,7 +2058,7 @@
 
 
 		pEquipment->init();
-		LOGE("已添加“EFEM(ROBOT)”.");
+		LOGE("宸叉坊鍔犫�淓FEM(ROBOT)鈥�.");
 
 		return pEquipment;
 	}
@@ -1874,7 +2074,7 @@
 
 
 		pEquipment->init();
-		LOGE("已添加“ARM”.");
+		LOGE("宸叉坊鍔犫�淎RM鈥�.");
 
 		return pEquipment;
 	}
@@ -1890,12 +2090,12 @@
 
 
 		pEquipment->init();
-		LOGE("已添加“%s”.", pEquipment->getName().c_str());
+		LOGE("宸叉坊鍔犫��%s鈥�.", pEquipment->getName().c_str());
 
 		return pEquipment;
 	}
 
-	/* 添加bonder1 或 bonder2 
+	/* 娣诲姞bonder1 鎴� bonder2 
 	 * index -- 0, bonder1
 	 * index -- 1, bonder2
 	 */
@@ -1914,7 +2114,7 @@
 
 
 		pEquipment->init();
-		LOGE("已添加“%s”.", pEquipment->getName().c_str());
+		LOGE("宸叉坊鍔犫��%s鈥�.", pEquipment->getName().c_str());
 
 
 		return pEquipment;
@@ -1932,7 +2132,7 @@
 		addToEquipmentList(pEquipment);
 
 		pEquipment->init();
-		LOGE("已添加“Aligner”.");
+		LOGE("宸叉坊鍔犫�淎ligner鈥�.");
 
 		return pEquipment;
 	}
@@ -1949,7 +2149,7 @@
 		addToEquipmentList(pEquipment);
 
 		pEquipment->init();
-		LOGE("已添加“Measurement”.");
+		LOGE("宸叉坊鍔犫�淢easurement鈥�.");
 
 		return pEquipment;
 	}
@@ -1964,7 +2164,7 @@
 		static int i = 0;
 		i++;
 
-		// 自动保存缓存
+		// 鑷姩淇濆瓨缂撳瓨
 		if (i % (4 * 2) == 0) {
 			if (m_bDataModify) {
 				saveCacheAndBackups();
@@ -1973,8 +2173,203 @@
 		}
 
 
+		// 妯℃嫙娴嬭瘯锛堟棤鏈哄櫒鑱旀満鏃剁敤浜庤仈璋� EAP锛�
+		// 璇诲彇 test.ini锛堝綋鍓嶇洰褰曟垨 exe 鍚岀洰褰曪級
+		{
+			struct SimCfg {
+				bool enabled{ false };
+				DWORD intervalMs{ 5000 };
+				int step{ 0 };
+			};
+			auto loadCfg = [&]() -> SimCfg {
+				SimCfg cfg;
 
-		// 模拟测试
+				// Try INI: current dir, then exe dir
+				char iniPath[MAX_PATH] = { 0 };
+				strcpy_s(iniPath, "test.ini");
+				auto readIni = [&](const char* path) -> bool {
+					const UINT en = GetPrivateProfileIntA("SimEap", "Enabled", 0, path);
+					if (en == 0) return false; // treat as missing/disabled
+					cfg.enabled = (en != 0);
+					cfg.intervalMs = (DWORD)GetPrivateProfileIntA("SimEap", "IntervalMs", 5000, path);
+					cfg.intervalMs = max(500u, cfg.intervalMs);
+					cfg.step = (int)GetPrivateProfileIntA("SimEap", "Step", 0, path);
+					return true;
+				};
+				if (!readIni(iniPath)) {
+					char exePath[MAX_PATH] = { 0 };
+					GetModuleFileNameA(NULL, exePath, MAX_PATH);
+					char* lastSlash = strrchr(exePath, '\\');
+					if (lastSlash != nullptr) {
+						*(lastSlash + 1) = '\0';
+						strcat_s(exePath, "test.ini");
+						readIni(exePath);
+					}
+				}
+				return cfg;
+			};
+
+			const SimCfg cfg = loadCfg();
+			if (cfg.enabled) {
+				static DWORD lastTick = 0;
+				static int lastExecutedStep = -1;
+				static bool inited = false;
+				static SERVO::CGlass simGlass;
+				static SERVO::CVcrEventReport simVcr;
+				static SERVO::CProcessJob simPj("PJ1001");
+				static SERVO::CControlJob simCj("CJ5007");
+
+				if (!inited) {
+					inited = true;
+					simGlass.setID("SIM_PANEL_001");
+					simVcr.getGlassId() = "SIM_PANEL_001";
+				}
+
+				DWORD now = GetTickCount();
+				if (lastTick == 0) lastTick = now;
+				if ((now - lastTick) < cfg.intervalMs) {
+					return;
+				}
+				lastTick = now;
+
+				// 鍗曟瑙﹀彂锛氭瘡涓� Step 鍙墽琛屼竴娆★紱浣犳墜鍔ㄤ慨鏀� ini 鐨� Step 鍊煎悗浼氬啀娆¤Е鍙�
+				const int step = cfg.step;
+				if (step <= 0 || step == lastExecutedStep) {
+					return;
+				}
+				lastExecutedStep = step;
+
+				// 鍙栦竴涓� LoadPort 浣滀负妯℃嫙鐩爣
+				SERVO::CLoadPort* pLpEq = (SERVO::CLoadPort*)getEquipment(EQ_ID_LOADPORT1);
+
+				auto fireLoadPortStatus = [&](short status) {
+					pLpEq->simulateSetCassetteId("Test-Cassette-001");
+					if (m_listener.onLoadPortStatusChanged != nullptr && pLpEq != nullptr) {
+						m_listener.onLoadPortStatusChanged(this, pLpEq, status, 0);
+					}
+				};
+				auto fireProcessState = [&](SERVO::CEquipment* pEq, int slotNo, SERVO::PROCESS_STATE st) {
+					// Drive equipment state so listeners receive prev/current states consistently.
+					if (pEq != nullptr) {
+						pEq->fireSetProcessState(slotNo, st);
+					}
+				};
+
+				LOGI("<Master>SIM_EAP single-step=%d", step);
+				switch (step) {
+					// ===== 涓氬姟娴佺▼姝ラ锛�1~23锛�=====
+				case 1: // E87_06 Material Arrived(TransferBlock) -> Port Blocked
+					fireLoadPortStatus(PORT_BLOCKED);
+					break;
+				case 2: // E87_03 CarrierID Readed -> Port InUse
+					fireLoadPortStatus(PORT_INUSE);
+					break;
+				case 3: // S1F3 Query CJ Space (Host->EQ) - wait host
+					LOGI("<Master>SIM_EAP step3: wait host S1F3");
+					break;
+				case 4: // S16F21 Query PJ Space (Host->EQ) - wait host
+					LOGI("<Master>SIM_EAP step4: wait host S16F21");
+					break;
+				case 5: // S7F19 Query PPID List (Host->EQ) - wait host
+					LOGI("<Master>SIM_EAP step5: wait host S7F19");
+					break;
+				case 6: // S3F17 ProceedWithCarrier (Host->EQ) - wait host
+					LOGI("<Master>SIM_EAP step6: wait host S3F17 ProceedWithCarrier");
+					break;
+				case 7: // E87_14 Check SlotMap (璁惧涓婃姤/杩涘叆 WFH) - 鐢� PORT_INUSE 鍐呴儴瑙﹀彂
+					fireLoadPortStatus(PORT_INUSE);
+					break;
+				case 8: // S3F17 ProceedWithSlotMap (Host->EQ) - wait host
+					LOGI("<Master>SIM_EAP step8: wait host S3F17 ProceedWithSlotMap");
+					break;
+				case 9: // SlotMap Verify OK (鏈」鐩湪鏀跺埌 ProceedWithSlotMap 鍚庝笂鎶�) - wait host
+					LOGI("<Master>SIM_EAP step9: wait host ProceedWithSlotMap to trigger VerifyOK");
+					break;
+				case 10: // Create PJ (Host->EQ) - wait host
+					LOGI("<Master>SIM_EAP step10: wait host S16F15 CreateMultiPJ");
+					break;
+				case 11: // PJ Queued锛堟湰椤圭洰鍦ㄥ垱寤� PJ 鍚庝笂鎶ワ級 - wait host
+					LOGI("<Master>SIM_EAP step11: wait host CreateMultiPJ to trigger PJ_Queued");
+					break;
+				case 12: // Create CJ (Host->EQ) - wait host
+					LOGI("<Master>SIM_EAP step12: wait host S14F9 CreateCJ");
+					break;
+				case 13: // CJ Start
+					if (m_listener.onCjStart != nullptr) m_listener.onCjStart(this, &simCj);
+					break;
+				case 14: // PJ Start
+					if (m_listener.onPjStart != nullptr) m_listener.onPjStart(this, &simPj);
+					break;
+				case 15: // OCR
+					if (m_listener.onEqVcrEventReport != nullptr && pLpEq != nullptr) {
+						m_listener.onEqVcrEventReport(this, pLpEq, &simVcr);
+					}
+					break;
+				case 16: // Panel Start
+					if (m_listener.onPanelStart != nullptr) m_listener.onPanelStart(this, &simGlass);
+					// 鍚屾椂瑙﹀彂涓�娆″瓙鏈哄彴寮�濮嬶紙绀轰緥锛欱onder1, slot 1锛�
+					fireProcessState(getEquipment(EQ_ID_Bonder1), 1, SERVO::PROCESS_STATE::Processing);
+					break;
+				case 17: // Panel End
+					// 鍚屾椂瑙﹀彂涓�娆″瓙鏈哄彴缁撴潫锛堢ず渚嬶細Bonder1, slot 1锛�
+					fireProcessState(getEquipment(EQ_ID_Bonder1), 1, SERVO::PROCESS_STATE::Complete);
+					if (m_listener.onPanelEnd != nullptr) m_listener.onPanelEnd(this, &simGlass);
+					break;
+				case 18: // PJ End
+					if (m_listener.onPjEnd != nullptr) m_listener.onPjEnd(this, &simPj);
+					break;
+				case 19: // CJ End
+					if (m_listener.onCjEnd != nullptr) m_listener.onCjEnd(this, &simCj);
+					break;
+				case 20: // Ready to Release (Port Unload Ready; with prev INUSE will also trigger ReadyToRelease)
+					fireLoadPortStatus(PORT_UNLOAD_READY);
+					break;
+				case 21: // CarrierRelease (Host->EQ) - optional / wait host
+					LOGI("<Master>SIM_EAP step21: wait host S3F17 CarrierRelease");
+					break;
+				case 22: // Ready to Unload
+					fireLoadPortStatus(PORT_UNLOAD_READY);
+					break;
+				case 23: // Material Removed (and ReadyToLoad)
+					fireLoadPortStatus(PORT_LOAD_READY);
+					fireLoadPortStatus(PORT_EMPTY); // will also raise LoadPortNotAssoc via Model
+					break;
+				case 24: { // 妯℃嫙 SV Data锛堢ず渚嬶細Bonder1锛�
+					SERVO::CEquipment* pEq = getEquipment(EQ_ID_Bonder1);
+					if (pEq != nullptr && m_listener.onSVDataReport != nullptr) {
+						static int counter = 0;
+						++counter;
+						std::vector<CParam> params;
+						params.emplace_back("MockSV_Temp", "1", "C", 25 + (counter % 5));
+						params.emplace_back("MockSV_Pressure", "2", "kPa", 100 + (counter % 3));
+						params.emplace_back("MockSV_Speed", "3", "mm/s", 50 + (counter % 7));
+						m_listener.onSVDataReport(this, pEq, params);
+						LOGI("<Master>SIM_EAP step24: mock SVData (Bonder1), params=%zu", params.size());
+					}
+					break;
+				}
+				case 25: { // 妯℃嫙 Process Data锛堢ず渚嬶細Bonder1锛�
+					SERVO::CEquipment* pEq = getEquipment(EQ_ID_Bonder1);
+					if (pEq != nullptr && m_listener.onProcessDataReport != nullptr) {
+						static int counter = 0;
+						++counter;
+						std::vector<CParam> params;
+						params.emplace_back("MockProc_CycleTime", "1", "s", 30 + (counter % 4));
+						params.emplace_back("MockProc_MaxTemp", "2", "C", 200 + (counter % 6));
+						params.emplace_back("MockProc_Result", "3", "", (counter % 2) ? 1 : 0);
+						m_listener.onProcessDataReport(this, pEq, params);
+						LOGI("<Master>SIM_EAP step25: mock ProcessData (Bonder1), params=%zu", params.size());
+					}
+					break;
+				}
+				default:
+					break;
+				}
+			}
+		}
+
+
+		// 妯℃嫙娴嬭瘯
 		/*
 		static int aaa = 0;
 		aaa++;
@@ -1985,7 +2380,7 @@
 				glassFromQueueToInPorcess(pGlass);
 				this->saveState();
 
-				// 这里上报Panel Start事件
+				// 杩欓噷涓婃姤Panel Start浜嬩欢
 				if (m_listener.onPanelStart != nullptr) {
 					m_listener.onPanelStart(this, pGlass);
 				}
@@ -2001,7 +2396,7 @@
 				glassFromInPorcessToComplete(pGlass);
 				this->saveState();
 
-				// 这里上报Panel End事件
+				// 杩欓噷涓婃姤Panel End浜嬩欢
 				if (m_listener.onPanelEnd != nullptr) {
 					m_listener.onPanelEnd(this, pGlass);
 				}
@@ -2010,17 +2405,17 @@
 				if (pJob != nullptr && checkAndUpdatePjComplete(pJob)) {
 					processJobFromInPorcessToComplete(pJob);
 					this->saveState();
-					LOGE("<Master>ProcessJob(%s)完成.",
+					LOGE("<Master>ProcessJob(%s)瀹屾垚.",
 						pJob->id().c_str());
 					if (m_listener.onPjEnd != nullptr) {
 						m_listener.onPjEnd(this, pJob);
 					}
 
-					// 检查CJ是否已经完成
+					// 妫�鏌J鏄惁宸茬粡瀹屾垚
 					ASSERT(m_pControlJob);
 					if (checkAndUpdateCjComplete(m_pControlJob)) {
 						this->saveState();
-						LOGE("<Master>ControlJob(%s)完成.",
+						LOGE("<Master>ControlJob(%s)瀹屾垚.",
 							m_pControlJob->id().c_str());
 						if (m_listener.onCjEnd != nullptr) {
 							m_listener.onCjEnd(this, pJob);
@@ -2049,63 +2444,63 @@
 
 		nRet = pLoadPort1->getPin("Out")->connectPin(pAligner->getPin("In1"));
 		if (nRet < 0) {
-			LOGE("连接LoadPort1-Fliper失败");
+			LOGE("杩炴帴LoadPort1-Fliper澶辫触");
 		}
 		nRet = pLoadPort2->getPin("Out")->connectPin(pAligner->getPin("In2"));
 		if (nRet < 0) {
-			LOGE("连接LoadPort1-Fliper失败");
+			LOGE("杩炴帴LoadPort1-Fliper澶辫触");
 		}
 
 		nRet = pAligner->getPin("Out1")->connectPin(pFliper->getPin("In"));
 		if (nRet < 0) {
-			LOGE("连接Aligner-Fliper失败");
+			LOGE("杩炴帴Aligner-Fliper澶辫触");
 		}
 		nRet = pAligner->getPin("Out2")->connectPin(pVacuumBake->getPin("In"));
 		if (nRet < 0) {
-			LOGE("连接Aligner-VacuumBake失败");
+			LOGE("杩炴帴Aligner-VacuumBake澶辫触");
 		}
 
 		nRet = pFliper->getPin("Out1")->connectPin(pBonder1->getPin("In1"));
 		if (nRet < 0) {
-			LOGE("连接Fliper-Bonder1失败");
+			LOGE("杩炴帴Fliper-Bonder1澶辫触");
 		}
 		nRet = pFliper->getPin("Out2")->connectPin(pBonder2->getPin("In1"));
 		if (nRet < 0) {
-			LOGE("连接Fliper-Bonder2失败");
+			LOGE("杩炴帴Fliper-Bonder2澶辫触");
 		}
 
 		nRet = pVacuumBake->getPin("Out1")->connectPin(pBonder1->getPin("In2"));
 		if (nRet < 0) {
-			LOGE("连接VacuumBake-Bonder1失败");
+			LOGE("杩炴帴VacuumBake-Bonder1澶辫触");
 		}
 		nRet = pVacuumBake->getPin("Out2")->connectPin(pBonder2->getPin("In2"));
 		if (nRet < 0) {
-			LOGE("连接VacuumBake-Bonder2失败");
+			LOGE("杩炴帴VacuumBake-Bonder2澶辫触");
 		}
 
 		nRet = pBonder1->getPin("Out")->connectPin(pBakeCooling->getPin("In1"));
 		if (nRet < 0) {
-			LOGE("连接Bonder1-BakeCooling失败");
+			LOGE("杩炴帴Bonder1-BakeCooling澶辫触");
 		}
 
 		nRet = pBonder2->getPin("Out")->connectPin(pBakeCooling->getPin("In2"));
 		if (nRet < 0) {
-			LOGE("连接Bonder2-BakeCooling失败");
+			LOGE("杩炴帴Bonder2-BakeCooling澶辫触");
 		}
 
 		nRet = pBakeCooling->getPin("Out")->connectPin(pMeasurement->getPin("In"));
 		if (nRet < 0) {
-			LOGE("连接BakeCooling-LoadPort3失败");
+			LOGE("杩炴帴BakeCooling-LoadPort3澶辫触");
 		}
 
 		nRet = pMeasurement->getPin("Out1")->connectPin(pLoadPort3->getPin("In"));
 		if (nRet < 0) {
-			LOGE("连接BakeCooling-LoadPort3失败");
+			LOGE("杩炴帴BakeCooling-LoadPort3澶辫触");
 		}
 
 		nRet = pMeasurement->getPin("Out2")->connectPin(pLoadPort4->getPin("In"));
 		if (nRet < 0) {
-			LOGE("连接BakeCooling-LoadPort4失败");
+			LOGE("杩炴帴BakeCooling-LoadPort4澶辫触");
 		}
 	}
 
@@ -2129,7 +2524,7 @@
 		saveCache();
 
 
-		// 创建备份目录
+		// 鍒涘缓澶囦唤鐩綍
 		CString strNewFile;
 		CString strFileDir = m_strFilepath.c_str();
 		int index = strFileDir.ReverseFind('\\');
@@ -2383,8 +2778,8 @@
 		}
 		unlock();
 
-		// 当前任务手动中止后,停止调度,需要操作员在解决问题后,重新启动
-		// 25年7月23日后修改为不停止任务
+		// 褰撳墠浠诲姟鎵嬪姩涓鍚庯紝鍋滄璋冨害锛岄渶瑕佹搷浣滃憳鍦ㄨВ鍐抽棶棰樺悗锛岄噸鏂板惎鍔�
+		// 25骞�7鏈�23鏃ュ悗淇敼涓轰笉鍋滄浠诲姟
 		// stop();
 
 		return 0;
@@ -2542,15 +2937,31 @@
 		}
 		m_processJobs = temp;
 
+		// 閲嶇疆鍚勭鍙� DownloadMap锛圚ost/鏈湴鍕鹃�夌殑鏈熸湜鍔犲伐妲戒綅锛�
+		for (int i = 0; i < 4; i++) {
+			auto* pPort = (CLoadPort*)getEquipment(EQ_ID_LOADPORT1 + i);
+			if (pPort != nullptr) {
+				pPort->setDownloadCassetteMap(0);
+			}
+		}
 
-		// 更新context
-		std::vector<uint8_t> newSlots;
-		std::vector<void*> newContexts;
+
+		// 鏇存柊context
 		for (auto pj : m_processJobs) {
 			for (auto& c : pj->carriers()) {
 				auto pPort = getPortWithCarrierId(c.carrierId);
 				if (pPort == nullptr) continue;
 
+				short downloadMap = 0;
+				for (auto s : c.slots) {
+					if (s >= 1 && s <= 8) {
+						downloadMap |= (short)(1 << (s - 1));
+					}
+				}
+				pPort->setDownloadCassetteMap((short)(pPort->getDownloadCassetteMap() | downloadMap));
+
+				std::vector<uint8_t> newSlots;
+				std::vector<void*> newContexts;
 				for (auto s : c.slots) {
 					auto pGlass = pPort->getGlassFromSlot(s);
 					if (pGlass == nullptr) continue;
@@ -2566,6 +2977,9 @@
 
 
 		this->saveState();
+		if (m_listener.onControlJobChanged) {
+			notifyControlJobChanged();
+		}
 
 		return (int)m_processJobs.size();
 	}
@@ -2586,23 +3000,23 @@
 
 	int CMaster::setControlJob(CControlJob& controlJob)
 	{
-		// 回调:是否参创建ControlJob
+		// 鍥炶皟锛氭槸鍚﹀弬鍒涘缓ControlJob
 		auto canCreateCjFn = [&](uint32_t& cc, std::string& mm) -> bool {
 			if (m_pControlJob != nullptr) {
 				cc = 1100;
-				mm = "当前ControlJob未结批,不能创建新的ControlJob";
+				mm = "褰撳墠ControlJob鏈粨鎵癸紝涓嶈兘鍒涘缓鏂扮殑ControlJob";
 				return false;
 			}
 			return true;
 		};
 
 
-		// 回调:是否存在
+		// 鍥炶皟锛氭槸鍚﹀瓨鍦�
 		auto pjExists = [&](const std::string& id) -> bool {
 			return getProcessJob(id) != nullptr;
 		};
 
-		// 回调:是否可加入 CJ(这里定义:必须是 Queued)
+		// 鍥炶皟锛氭槸鍚﹀彲鍔犲叆 CJ锛堣繖閲屽畾涔夛細蹇呴』鏄� Queued锛�
 		auto pjJoinable = [&](const std::string& id) -> bool {
 			auto pj = getProcessJob(id);
 			if (pj == nullptr) return false;
@@ -2623,6 +3037,9 @@
 		}
 		m_pControlJob->setPJs(temps);
 		this->saveState();
+		if (m_listener.onControlJobChanged) {
+			notifyControlJobChanged();
+		}
 
 
 		return 0;
@@ -2675,7 +3092,98 @@
 
 	bool CMaster::ceidDefined(uint32_t ceid) const
 	{
-		return true;
+		if (m_allowedCeids.empty()) return true; // backward compatible: treat as all allowed when not configured
+		return m_allowedCeids.find(ceid) != m_allowedCeids.end();
+	}
+
+	bool CMaster::raiseSoftAlarm(int alarmId,
+		const std::string& desc,
+		int level/* = -1*/,
+		int deviceId/* = 0*/,
+		int unitId/* = 0*/,
+		const char* deviceName/* = "Software"*/,
+		const char* unitName/* = "App"*/)
+	{
+		AlarmManager& alarmManager = AlarmManager::getInstance();
+		const AlarmInfo* info = alarmManager.getAlarmInfoByID(alarmId);
+
+		int severity = level;
+		if (severity < 0 && info != nullptr) {
+			severity = info->nAlarmLevel;
+		}
+		if (severity < 0) severity = 0; // fallback
+
+		std::string descText = desc;
+		if (descText.empty() && info != nullptr) {
+			descText = !info->strDescription.empty() ? info->strDescription : info->strAlarmText;
+		}
+		if (descText.empty()) {
+			descText = CToolUnits::formatString("Alarm %d", alarmId);
+		}
+
+		AlarmData alarmData;
+		alarmData.nId = alarmId;
+		alarmData.nSeverityLevel = severity;
+		alarmData.nDeviceId = deviceId;
+		alarmData.nUnitId = unitId;
+		alarmData.strDeviceName = deviceName;
+		alarmData.strUnitName = unitName;
+		alarmData.strStartTime = CToolUnits::timeToString2(CToolUnits::getTimestamp());
+		alarmData.strEndTime = "";
+		alarmData.strDescription = descText;
+
+		int nAlarmEventId = 0;
+		return alarmManager.addAlarm(alarmData, nAlarmEventId);
+	}
+
+	void CMaster::handleCollectionEvent(uint32_t ceid)
+	{
+		// 閬嶅巻褰撳墠 PJ锛屽懡涓� pauseEvents 鏃跺彲鍦ㄦ鎵╁睍鏆傚仠鍔ㄤ綔
+		bool pausedAny = false;
+		for (auto pj : m_processJobs) {
+			if (pj == nullptr) continue;
+			const auto& pauseList = pj->pauseEvents();
+			if (std::find(pauseList.begin(), pauseList.end(), ceid) != pauseList.end()) {
+				LOGW("<Master>PauseEvent hit: CEID=%u, PJ=%s, state=%d", ceid, pj->id().c_str(), (int)pj->state());
+				if (pj->pause()) {
+					LOGI("<Master>PJ paused by CEID=%u", ceid);
+					pausedAny = true;
+				}
+				if (m_pControlJob != nullptr && m_pControlJob->state() == CJState::Executing) {
+					if (m_pControlJob->pause()) {
+						LOGI("<Master>ControlJob paused by CEID=%u", ceid);
+						pausedAny = true;
+					}
+				}
+			}
+		}
+		if (pausedAny && m_listener.onControlJobChanged) {
+			// 閫氱煡搴旂敤灞傚埛鏂� UI/鎸夐挳鐘舵��
+			notifyControlJobChanged();
+		}
+		if (pausedAny && !m_bPauseAlarmRaised) {
+			std::string desc = CToolUnits::formatString("<PauseEvent CEID=%u>", ceid);
+			bool raised = false;
+			if (m_pModelCtx != nullptr) {
+				raised = m_pModelCtx->raiseSoftAlarm(ALID_SOFTWARE_PAUSE_EVENT, desc);
+			}
+			else {
+				raised = raiseSoftAlarm(ALID_SOFTWARE_PAUSE_EVENT, desc);
+			}
+			if (raised) {
+				LOGI("<Master>PauseEvent soft alarm raised, CEID=%u", ceid);
+				m_bPauseAlarmRaised = true;
+			}
+		}
+	}
+
+	void CMaster::setAllowedCeids(const std::vector<unsigned int>& ceids)
+	{
+		m_allowedCeids.clear();
+		m_allowedCeids.reserve(ceids.size());
+		for (auto id : ceids) {
+			m_allowedCeids.insert(id);
+		}
 	}
 
 	bool CMaster::saveState() const
@@ -2683,27 +3191,27 @@
 		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
+		// 淇濆瓨 ControlJob
 		bool hasCJ = (m_pControlJob != nullptr);
 		ofs.write(reinterpret_cast<const char*>(&hasCJ), sizeof(hasCJ));
 		if (hasCJ) {
 			m_pControlJob->serialize(ofs);
 		}
 
-		// 保存 ProcessJob 列表
+		// 淇濆瓨 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;
 	}
 
@@ -2712,14 +3220,14 @@
 		std::ifstream ifs(m_strStatePath, 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;
 		}
 
@@ -2728,7 +3236,7 @@
 			m_pControlJob = nullptr;
 		}
 
-		// 读取 ControlJob
+		// 璇诲彇 ControlJob
 		bool hasCJ = false;
 		ifs.read(reinterpret_cast<char*>(&hasCJ), sizeof(hasCJ));
 		if (hasCJ) {
@@ -2739,7 +3247,7 @@
 			return false;
 		}
 
-		// 读取 ProcessJob 列表
+		// 璇诲彇 ProcessJob 鍒楄〃
 		uint32_t count = 0;
 		ifs.read(reinterpret_cast<char*>(&count), sizeof(count));
 		m_processJobs.clear();
@@ -2750,7 +3258,7 @@
 		}
 
 
-		// 找到CProcessJob指针加入列表中
+		// 鎵惧埌CProcessJob鎸囬拡鍔犲叆鍒楄〃涓�
 		std::vector<CProcessJob*> tempPjs;
 		auto ids = m_pControlJob->pjIds();
 		for (auto id : ids) {
@@ -2762,7 +3270,7 @@
 		m_pControlJob->setPJs(tempPjs);
 
 
-		// 更新contexts
+		// 鏇存柊contexts
 		auto pjs = m_pControlJob->getPjs();
 		for (auto pj : pjs) {
 			for (auto& c : pj->carriers()) {
@@ -2780,7 +3288,7 @@
 		}
 
 
-		// 如果版本升级,可在这里判断 version 来加载新字段
+		// 濡傛灉鐗堟湰鍗囩骇锛屽彲鍦ㄨ繖閲屽垽鏂� version 鏉ュ姞杞芥柊瀛楁
 
 
 		return true;
@@ -2808,7 +3316,7 @@
 	CGlass* CMaster::acquireNextGlass()
 	{
 		for (auto* pj : m_inProcesJobs) {
-			// 遍历 PJ 的 carriers 和 slots
+			// 閬嶅巻 PJ 鐨� carriers 鍜� slots
 			for (auto& cs : pj->carriers()) {
 				for (auto ctx : cs.contexts) {
 					CGlass* pGlass = (CGlass*)ctx;
@@ -2819,14 +3327,14 @@
 				}
 			}
 		}
-		return nullptr; // 没有可加工的 Glass
+		return nullptr; // 娌℃湁鍙姞宸ョ殑 Glass
 	}
 
 	int CMaster::acquireGlassToQueue()
 	{
 		int nCount = 0;
 		for (auto* pj : m_inProcesJobs) {
-			// 遍历 PJ 的 carriers 和 slots
+			// 閬嶅巻 PJ 鐨� carriers 鍜� slots
 			if (pj->carriers().empty()) continue;
 			for (auto& cs : pj->carriers()) {
 				for (auto ctx : cs.contexts) {
@@ -2946,7 +3454,7 @@
 
 
 
-		// 释放Job相关
+		// 閲婃斁Job鐩稿叧
 		for (auto item : m_processJobs) {
 			delete item;
 		}
@@ -2956,7 +3464,7 @@
 			m_pControlJob = nullptr;
 		}
 
-		// 注意要释放引用
+		// 娉ㄦ剰瑕侀噴鏀惧紩鐢�
 		m_inProcesJobs.clear();
 		m_completeProcessJobs.clear();
 		m_queueGlasses.clear();
@@ -2965,6 +3473,9 @@
 
 
 		saveState();
+		if (m_listener.onControlJobChanged) {
+			notifyControlJobChanged();
+		}
 
 		return true;
 	}
@@ -2980,7 +3491,7 @@
 		m_pControlJob->abort(description);
 
 
-		// 释放Job相关
+		// 閲婃斁Job鐩稿叧
 		for (auto item : m_processJobs) {
 			delete item;
 		}
@@ -2990,7 +3501,7 @@
 			m_pControlJob = nullptr;
 		}
 
-		// 注意要释放引用
+		// 娉ㄦ剰瑕侀噴鏀惧紩鐢�
 		m_inProcesJobs.clear();
 		m_completeProcessJobs.clear();
 		m_queueGlasses.clear();
@@ -2999,6 +3510,9 @@
 
 
 		saveState();
+		if (m_listener.onControlJobChanged) {
+			notifyControlJobChanged();
+		}
 
 		return true;
 	}
@@ -3047,7 +3561,7 @@
 	{
 		if (stop() == 0) {
 			m_nLastError = ER_CODE_AOI_NG;
-			m_strLastError = "AOI检测未通过.";
+			m_strLastError = "AOI妫�娴嬫湭閫氳繃.";
 		}
 	}
 
@@ -3060,6 +3574,15 @@
 		if (pSlot == nullptr) return false;
 
 		CGlass* pGlass = (CGlass*)pSlot->getContext();
+		if (pGlass == nullptr) return false;
+
+		// Buffer 涓婇檺涓� 1锛氭柊鎼嚭鏃朵涪寮冩棫鐨�
+		if (!m_bufGlass.empty()) {
+			for (auto* oldGlass : m_bufGlass) {
+				if (oldGlass != nullptr) oldGlass->release();
+			}
+			m_bufGlass.clear();
+		}
 		m_bufGlass.push_back(pGlass);
 		pGlass->addRef();
 		pSlot->setContext(nullptr);
@@ -3121,7 +3644,7 @@
 
 		};
 
-		// 事件:有人连入/断开就上日志
+		// 浜嬩欢锛氭湁浜鸿繛鍏�/鏂紑灏变笂鏃ュ織
 		auto clieintEventCallback = [](const std::string& ip, uint16_t port, bool connected) {
 			LOGI("<DAQBridge>[Client %s] %s:%u", connected ? _T("JOIN") : _T("LEAVE"), ip.c_str(), port);
 		};
@@ -3134,31 +3657,73 @@
 			m_pCollector->createServer(8081);
 			m_pCollector->startLoop(10);
 
-			// 1) 注册机台(推荐:先注册 id + 机器名称)
+			// 1) 娉ㄥ唽鏈哄彴锛堟帹鑽愶細鍏堟敞鍐� id + 鏈哄櫒鍚嶇О锛�
 			RetentionPolicy defP; defP.mode = RetainMode::ByCount; defP.maxSamples = 200;
-			m_pCollector->registryAddMachine(EQ_ID_Bonder1, "Bonder1", defP);
-			m_pCollector->registryAddMachine(EQ_ID_Bonder2, "Bonder2", defP);
-			m_pCollector->registryAddMachine(EQ_ID_VACUUMBAKE, "前烘烤", defP);
-			m_pCollector->registryAddMachine(EQ_ID_BAKE_COOLING, "烘烤冷却", defP);
+			m_pCollector->registryAddMachine(MID_Bonder1, "Bonder1", defP);
+			m_pCollector->registryAddMachine(MID_Bonder2, "Bonder2", defP);
+			m_pCollector->registryAddMachine(MID_VacuumBakeA, "鍓嶇儤-A", defP);
+			m_pCollector->registryAddMachine(MID_VacuumBakeB, "鍓嶇儤-B", defP);
+			m_pCollector->registryAddMachine(MID_BakeCoolingA, "鍚庣儤-A", defP);
+			m_pCollector->registryAddMachine(MID_BakeCoolingB, "鍚庣儤-B", defP);
 
 
-			// 2) 为通道设置“曲线名称”
+			// 2) 涓洪�氶亾璁剧疆鈥滄洸绾垮悕绉扳��
 			auto& dataTypes = CServoUtilsTool::getEqDataTypes();
-			auto& bonderTypes = dataTypes[EQ_ID_Bonder1];
+			auto& bonderTypes = dataTypes[MID_Bonder1];
 			for (size_t i = 0; i < bonderTypes.size(); ++i) {
-				m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, i + 1, bonderTypes[i].c_str());
-				m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, i + 1, bonderTypes[i].c_str());
+				m_pCollector->buffersSetChannelName(MID_Bonder1, (UINT)i + 1, bonderTypes[(UINT)i].c_str());
+				m_pCollector->buffersSetChannelName(MID_Bonder2, (UINT)i + 1, bonderTypes[(UINT)i].c_str());
 			}
 
-			auto& vacuumbakeTypes = dataTypes[EQ_ID_VACUUMBAKE];
+			auto& vacuumbakeTypes = dataTypes[MID_VacuumBakeA];
 			for (size_t i = 0; i < vacuumbakeTypes.size(); ++i) {
-				m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, i + 1, vacuumbakeTypes[i].c_str());
+				m_pCollector->buffersSetChannelName(MID_VacuumBakeA, (UINT)i + 1, vacuumbakeTypes[(UINT)i].c_str());
+				m_pCollector->buffersSetChannelName(MID_VacuumBakeB, (UINT)i + 1, vacuumbakeTypes[(UINT)i].c_str());
 			}
 
-			auto& coolingTypes = dataTypes[EQ_ID_BAKE_COOLING];
+			auto& coolingTypes = dataTypes[MID_BakeCoolingA];
 			for (size_t i = 0; i < coolingTypes.size(); ++i) {
-				m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, i + 1, coolingTypes[i].c_str());
+				m_pCollector->buffersSetChannelName(MID_BakeCoolingA, i + 1, coolingTypes[i].c_str());
+				m_pCollector->buffersSetChannelName(MID_BakeCoolingB, i + 1, coolingTypes[i].c_str());
+			}
+
+			if (m_curveMode == CurveMode::EmptyChamber) {
+				const uint32_t mids[] = {
+					MID_Bonder1, MID_Bonder2,
+					MID_VacuumBakeA, MID_VacuumBakeB,
+					MID_BakeCoolingA, MID_BakeCoolingB
+				};
+				for (uint32_t mid : mids) {
+					m_pCollector->batchStart(mid, "EMPTY_CHAMBER", 10 * 60 * 1000ULL);
+				}
 			}
 		}
 	}
+
+	uint32_t CMaster::SlotToMid(int eqid, int slot)
+	{
+		if (eqid == EQ_ID_Bonder1) {
+			return MID_Bonder1;
+		}
+
+		if (eqid == EQ_ID_Bonder2) {
+			return MID_Bonder2;
+		}
+
+		if (eqid == EQ_ID_VACUUMBAKE) {
+			if(slot == 1)
+				return MID_VacuumBakeA;
+			if (slot == 2)
+				return MID_VacuumBakeB;
+		}
+
+		if (eqid == EQ_ID_BAKE_COOLING) {
+			if (slot == 1)
+				return MID_BakeCoolingA;
+			if (slot == 3)
+				return MID_BakeCoolingB;
+		}
+
+		return 0;
+	}
 }
diff --git a/SourceCode/Bond/Servo/CMaster.h b/SourceCode/Bond/Servo/CMaster.h
index e2844bd..3db1a19 100644
--- a/SourceCode/Bond/Servo/CMaster.h
+++ b/SourceCode/Bond/Servo/CMaster.h
@@ -1,5 +1,6 @@
 锘�#pragma once
 #include <list>
+#include <unordered_set>
 #include "CEquipment.h"
 #include "CEFEM.h"
 #include "CBonder.h"
@@ -16,6 +17,9 @@
 #include "ProcessJob.h"
 #include "CControlJob.h"
 #include "../DAQBridge/core/Collector.h"
+#include "CJobDataS.h"
+
+class CModel;
 
 
 #define CTStep_Unknow                   0
@@ -50,6 +54,11 @@
         ATHERERROR
     };
 
+    enum class CurveMode {
+        Production = 0,
+        EmptyChamber
+    };
+
     typedef std::function<void(void* pMaster, MASTERSTATE state)> ONMASTERSTATECHANGED;
     typedef std::function<void(void* pMaster, CEquipment* pEiuipment, BOOL bAlive)> ONEQALIVE;
     typedef std::function<void(CStep* pStep, int code, void* pData)> ONEQSTEPEVENT;
@@ -58,8 +67,14 @@
     typedef std::function<void(void* pMaster, CEquipment* pEquipment, int code)> ONEQDATACHANGED;
     typedef std::function<void(void* pMaster, CRobotTask* pTask, int code)> ONROBOTTASKEVENT;
     typedef std::function<void(void* pMaster, CEquipment* pEquipment, short status, __int64 data)> ONLOADPORTSTATUSCHANGED;
+    typedef std::function<void(void* pMaster, CEquipment* pEquipment, int slotNo, PROCESS_STATE prevState, PROCESS_STATE state)> ONPROCESSSTATECHANGED;
+    typedef std::function<void(void* pMaster, CEquipment* pEquipment, const std::vector<CParam>& params)> ONPROCESSDATAREPORTEX;
+    typedef std::function<void(void* pMaster, CEquipment* pEquipment, const std::vector<CParam>& params)> ONSVDATAREPORT;
+    typedef std::function<void(void* pMaster, CEquipment* pEquipment, int port, CJobDataS* pJobDataS)> ONJOBRECEIVED;
+    typedef std::function<void(void* pMaster, CEquipment* pEquipment, int port, CJobDataS* pJobDataS)> ONJOBSENTOUT;
     typedef std::function<void(void* pMaster, int round)> ONCTROUNDEND;
     typedef std::function<void(void* pMaster, void* pj)> ONPJSTART;
+    typedef std::function<void(void* pMaster)> ONCONTROLJOBCHANGED;
     typedef struct _MasterListener
     {
         ONMASTERSTATECHANGED    onMasterStateChanged;
@@ -70,6 +85,11 @@
         ONEQDATACHANGED         onEqDataChanged;
         ONROBOTTASKEVENT        onRobotTaskEvent;
         ONLOADPORTSTATUSCHANGED	onLoadPortStatusChanged;
+        ONPROCESSSTATECHANGED   onProcessStateChanged;
+        ONSVDATAREPORT          onSVDataReport;
+        ONPROCESSDATAREPORTEX   onProcessDataReport;
+        ONJOBRECEIVED           onJobReceived;
+        ONJOBSENTOUT            onJobSentOut;
         ONCTROUNDEND            onCTRoundEnd;
         ONPJSTART               onCjStart;
         ONPJSTART               onCjEnd;
@@ -77,6 +97,7 @@
         ONPJSTART               onPjEnd;
         ONPJSTART               onPanelStart;
         ONPJSTART               onPanelEnd;
+        ONCONTROLJOBCHANGED     onControlJobChanged;
     } MasterListener;
 
     class CMaster : public IResourceView
@@ -87,6 +108,7 @@
 
 
     public:
+        void setModelCtx(CModel* pModel);
         void setListener(MasterListener listener);
         CRobotTask* getActiveRobotTask();
         int init();
@@ -98,6 +120,8 @@
         void clearError();
         ULONGLONG getRunTime();
         MASTERSTATE getState();
+        void setCurveMode(CurveMode mode);
+        CurveMode getCurveMode() const;
         unsigned DispatchProc();
         unsigned ReadBitsProc();
         void onTimer(UINT nTimerid);
@@ -139,6 +163,7 @@
         int getPortCassetteSnSeed(int port);
         void setPortCassetteSnSeed(int port, int seed);
         CGlass* getGlass(int scrPort, int scrSlot);
+        uint32_t SlotToMid(int eqid, int slot);
 
     private:
         inline void lock() { EnterCriticalSection(&m_criticalSection); }
@@ -177,6 +202,15 @@
         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;
+        void setAllowedCeids(const std::vector<unsigned int>& ceids);
+        void handleCollectionEvent(uint32_t ceid);
+        bool raiseSoftAlarm(int alarmId,
+            const std::string& desc,
+            int level = -1,
+            int deviceId = 0,
+            int unitId = 0,
+            const char* deviceName = "Software",
+            const char* unitName = "App");
 
     public:
         int getLastError();
@@ -200,7 +234,7 @@
         bool canCompleteControlJob();
         bool canDeleteControlJob();
         
-        // DAQ Bridge閻╃鍙�
+        // DAQ Bridge 鐩稿叧
         Collector* getCollector() const { return m_pCollector; }
 
     private:
@@ -229,6 +263,7 @@
         ULONGLONG m_ullStartTime;
         ULONGLONG m_ullRunTime;
         MASTERSTATE m_state;
+        CurveMode m_curveMode;
 
         // 褰撳墠浠诲姟鍜屽凡瀹屾垚浠诲姟鍒楄〃
         CRobotTask* m_pActiveRobotTask;
@@ -238,10 +273,9 @@
         int m_nLastError;
         std::string m_strLastError;
 
-        // 鍦ㄥ紑濮嬪伐鑹哄墠鏄惁鍏堥渶瑕佸厛姣旇緝map
+        // 鍦ㄥ紑濮嬪伐鑹哄墠鏄惁闇�瑕佸厛姣旇緝 map
         BOOL m_isCompareMapsBeforeProceeding;
         BOOL m_bJobMode;
-
 
         // 鍗冧紶鍦堟暟璁℃暟
         int m_nContinuousTransferCount;
@@ -249,7 +283,7 @@
         int m_nContinuousWorkingPort;
         int m_nContinuousWorkingSlot;
 
-        // 鏂板宸茬粡寮�濮嬪鐞嗙殑ProcessJob鍒楄〃
+        // 宸茬粡寮�濮嬪鐞嗙殑 ProcessJob 鍒楄〃
         std::vector<CProcessJob*> m_inProcesJobs;
         std::vector<CProcessJob*> m_completeProcessJobs;
         std::vector<CGlass*> m_queueGlasses;
@@ -259,16 +293,23 @@
     private:
         bool m_bEnableEventReport;
         bool m_bEnableAlarmReport;
+        bool m_bPauseAlarmRaised;
         SERVO::CControlJob* m_pControlJob;
         std::vector<SERVO::CProcessJob*> m_processJobs;
         std::string m_strStatePath;
+        CModel* m_pModelCtx;
 
         int m_nTestFlag;
         std::list<CGlass*> m_bufGlass;
+        std::unordered_set<uint32_t> m_allowedCeids;
 
     private:
         Collector* m_pCollector = nullptr;
         void CreateDAQBridgeServer();
+        inline void notifyControlJobChanged() {
+            if (m_listener.onControlJobChanged) {
+                m_listener.onControlJobChanged(this);
+            }
+        }
     };
 }
-
diff --git a/SourceCode/Bond/Servo/CMyStatusbar.cpp b/SourceCode/Bond/Servo/CMyStatusbar.cpp
index 589a8cc..7aa389a 100644
--- a/SourceCode/Bond/Servo/CMyStatusbar.cpp
+++ b/SourceCode/Bond/Servo/CMyStatusbar.cpp
@@ -70,6 +70,11 @@
 	SetDlgItemText(IDC_LABEL_RUNTIME, pszText);
 }
 
+void CMyStatusbar::setJobText(const char* pszText)
+{
+	SetDlgItemText(IDC_LABEL_JOBSTATE, pszText);
+}
+
 void CMyStatusbar::setCurTaskBtnText(const char* pszText)
 {
 	SetDlgItemText(IDC_BUTTON_ROBOTTASK, pszText);
@@ -78,6 +83,24 @@
 void CMyStatusbar::setCimBtnText(const char* pszText)
 {
 	SetDlgItemText(IDC_BUTTON_CIM, pszText);
+}
+
+void CMyStatusbar::setCurTaskBtnColors(COLORREF face, COLORREF frame, COLORREF text)
+{
+	m_btnCurTask.SetFaceColor(face);
+	m_btnCurTask.SetFrameColor(frame);
+	m_btnCurTask.SetTextColor(text);
+	Invalidate();
+	UpdateWindow();
+}
+
+void CMyStatusbar::setCimBtnColors(COLORREF face, COLORREF frame, COLORREF text)
+{
+	m_btnCim.SetFaceColor(face);
+	m_btnCim.SetFrameColor(frame);
+	m_btnCim.SetTextColor(text);
+	Invalidate();
+	UpdateWindow();
 }
 
 BOOL CMyStatusbar::OnInitDialog()
@@ -91,12 +114,14 @@
 	m_btnCurTask.SetFrameColor(m_crBkgnd);
 	m_btnCurTask.SetFrameColor(BS_HOVER, RGB(218, 218, 218));
 	m_btnCurTask.SetFrameColor(BS_PRESS, RGB(168, 168, 168));
+	m_btnCurTask.SetTextColor(m_crForeground);
 
 	m_btnCim.SubclassDlgItem(IDC_BUTTON_CIM, this);
 	m_btnCim.SetFaceColor(m_crBkgnd);
 	m_btnCim.SetFrameColor(m_crBkgnd);
 	m_btnCim.SetFrameColor(BS_HOVER, RGB(218, 218, 218));
 	m_btnCim.SetFrameColor(BS_PRESS, RGB(168, 168, 168));
+	m_btnCim.SetTextColor(m_crForeground);
 
 	return TRUE;  // return TRUE unless you set the focus to a control
 				  // 寮傚父: OCX 灞炴�ч〉搴旇繑鍥� FALSE
@@ -192,4 +217,12 @@
 	pItem->GetClientRect(rcItem);
 	pItem->MoveWindow(x, (rcClient.Height() - rcItem.Height()) / 2, rcItem.Width(), rcItem.Height());
 	x += rcItem.Width();
-}
\ No newline at end of file
+
+	x += 8;
+	pItem = GetDlgItem(IDC_LABEL_JOBSTATE);
+	if (pItem != nullptr) {
+		pItem->GetClientRect(rcItem);
+		pItem->MoveWindow(x, (rcClient.Height() - rcItem.Height()) / 2, rcItem.Width(), rcItem.Height());
+		x += rcItem.Width();
+	}
+}
diff --git a/SourceCode/Bond/Servo/CMyStatusbar.h b/SourceCode/Bond/Servo/CMyStatusbar.h
index 4afaaa4..37f039d 100644
--- a/SourceCode/Bond/Servo/CMyStatusbar.h
+++ b/SourceCode/Bond/Servo/CMyStatusbar.h
@@ -20,8 +20,11 @@
 	void setBackgroundColor(COLORREF color);
 	void setForegroundColor(COLORREF cr);
 	void setRunTimeText(const char* pszText);
+	void setJobText(const char* pszText);
 	void setCurTaskBtnText(const char* pszText);
 	void setCimBtnText(const char* pszText);
+	void setCurTaskBtnColors(COLORREF face, COLORREF frame, COLORREF text);
+	void setCimBtnColors(COLORREF face, COLORREF frame, COLORREF text);
 
 private:
 	void Resize();
diff --git a/SourceCode/Bond/Servo/CPageCollectionEvent.cpp b/SourceCode/Bond/Servo/CPageCollectionEvent.cpp
index b7d3322..46f4b71 100644
--- a/SourceCode/Bond/Servo/CPageCollectionEvent.cpp
+++ b/SourceCode/Bond/Servo/CPageCollectionEvent.cpp
@@ -5,6 +5,7 @@
 #include "Servo.h"
 #include "CPageCollectionEvent.h"
 #include "afxdialogex.h"
+#include "CEventEditDlg.h"
 
 
 // CPageCollectionEvent 瀵硅瘽妗�
@@ -32,6 +33,7 @@
 	ON_WM_CTLCOLOR()
 	ON_WM_DESTROY()
 	ON_WM_SIZE()
+	ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST1, &CPageCollectionEvent::OnLvnItemchangedList1)
 END_MESSAGE_MAP()
 
 
@@ -123,3 +125,109 @@
 		m_listCtrl.SetItemText(index, 4, item->getReportIdsText().c_str());
 	}
 }
+
+void CPageCollectionEvent::OnCreateBtns()
+{
+	const int BTN_W = 80;
+	const int BTN_H = 28;
+	CreateBtn(_T("鏂板"), BTN_W, BTN_H, 3001);
+	CreateBtn(_T("鍒犻櫎"), BTN_W, BTN_H, 3002)->EnableWindow(FALSE);
+	CreateBtn(_T("缂栬緫"), BTN_W, BTN_H, 3003)->EnableWindow(FALSE);
+}
+
+void CPageCollectionEvent::OnLvnItemchangedList1(NMHDR* pNMHDR, LRESULT* pResult)
+{
+	LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
+	int nSelCount = m_listCtrl.GetSelectedCount();
+	if (CButton* pDel = GetBtnByName("鍒犻櫎")) {
+		pDel->EnableWindow(nSelCount > 0);
+	}
+	if (CButton* pEdit = GetBtnByName("缂栬緫")) {
+		pEdit->EnableWindow(nSelCount > 0);
+	}
+	*pResult = 0;
+}
+
+void CPageCollectionEvent::OnClickedBtn(const char* btnName)
+{
+	ASSERT(btnName);
+	if (_strcmpi(btnName, "鏂板") == 0) {
+		int rc = UX_CanExecute(L"addEvents");
+		if (rc != 1) {
+			AfxMessageBox("鎿嶄綔鏉冮檺涓嶈冻锛岃鑱旂郴绠$悊浜哄憳锛�");
+			return;
+		}
+		unsigned int newId = theApp.m_model.m_hsmsPassive.getMaxCollectionEventId() + 1;
+		std::vector<unsigned int> rptIds;
+		CEventEditDlg dlg(_T("鏂板浜嬩欢"), (int)newId, _T(""), _T(""), rptIds, this);
+		if (dlg.DoModal() != IDOK) return;
+
+		int ret = theApp.m_model.m_hsmsPassive.addCollectionEvent(newId, CT2A(dlg.GetNameText()), CT2A(dlg.GetDescText()), dlg.GetSelectedRptIds());
+		if (ret == 0) {
+			UX_RecordAction(L"addEvents");
+			m_listCtrl.DeleteAllItems();
+			loadCollectionEvents();
+			if (CButton* pDel = GetBtnByName("鍒犻櫎")) pDel->EnableWindow(FALSE);
+			if (CButton* pEdit = GetBtnByName("缂栬緫")) pEdit->EnableWindow(FALSE);
+		}
+		else {
+			AfxMessageBox(_T("鏂板浜嬩欢澶辫触锛堝彲鑳絀D閲嶅鎴栧啓鍏ュけ璐ワ級"));
+		}
+	}
+	else if (_strcmpi(btnName, "鍒犻櫎") == 0) {
+		POSITION pos = m_listCtrl.GetFirstSelectedItemPosition();
+		if (pos == nullptr) return;
+		int nItem = m_listCtrl.GetNextSelectedItem(pos);
+		auto pEvent = reinterpret_cast<SERVO::CCollectionEvent*>(m_listCtrl.GetItemData(nItem));
+		if (pEvent == nullptr) return;
+
+		int rc = UX_CanExecute(L"delEvents");
+		if (rc != 1) {
+			AfxMessageBox("鎿嶄綔鏉冮檺涓嶈冻锛岃鑱旂郴绠$悊浜哄憳锛�");
+			return;
+		}
+
+		int ret = theApp.m_model.m_hsmsPassive.deleteCollectionEvent((unsigned short)pEvent->getEventId());
+		if (ret == 0) {
+			UX_RecordAction(L"delEvents");
+			m_listCtrl.DeleteAllItems();
+			loadCollectionEvents();
+			if (CButton* pDel = GetBtnByName("鍒犻櫎")) pDel->EnableWindow(FALSE);
+			if (CButton* pEdit = GetBtnByName("缂栬緫")) pEdit->EnableWindow(FALSE);
+		}
+		else {
+			AfxMessageBox(_T("鍒犻櫎浜嬩欢澶辫触"));
+		}
+	}
+	else if (_strcmpi(btnName, "缂栬緫") == 0) {
+		POSITION pos = m_listCtrl.GetFirstSelectedItemPosition();
+		if (pos == nullptr) return;
+		int nItem = m_listCtrl.GetNextSelectedItem(pos);
+		auto pEvent = reinterpret_cast<SERVO::CCollectionEvent*>(m_listCtrl.GetItemData(nItem));
+		if (pEvent == nullptr) return;
+
+		int rc = UX_CanExecute(L"editEvents");
+		if (rc != 1) {
+			AfxMessageBox("鎿嶄綔鏉冮檺涓嶈冻锛岃鑱旂郴绠$悊浜哄憳锛�");
+			return;
+		}
+
+		CString name = pEvent->getName().c_str();
+		CString desc = pEvent->getDescription().c_str();
+		auto rptIds = pEvent->getReportIds();
+		CEventEditDlg dlg(_T("缂栬緫浜嬩欢"), (int)pEvent->getEventId(), name, desc, rptIds, this);
+		if (dlg.DoModal() != IDOK) return;
+
+		int ret = theApp.m_model.m_hsmsPassive.updateCollectionEvent(pEvent->getEventId(), CT2A(dlg.GetNameText()), CT2A(dlg.GetDescText()), dlg.GetSelectedRptIds());
+		if (ret == 0) {
+			UX_RecordAction(L"editEvents");
+			m_listCtrl.DeleteAllItems();
+			loadCollectionEvents();
+			if (CButton* pDel = GetBtnByName("鍒犻櫎")) pDel->EnableWindow(FALSE);
+			if (CButton* pEdit = GetBtnByName("缂栬緫")) pEdit->EnableWindow(FALSE);
+		}
+		else {
+			AfxMessageBox(_T("缂栬緫浜嬩欢澶辫触锛堝彲鑳藉啓鍏ュけ璐ワ級"));
+		}
+	}
+}
diff --git a/SourceCode/Bond/Servo/CPageCollectionEvent.h b/SourceCode/Bond/Servo/CPageCollectionEvent.h
index 7332a09..836fb6f 100644
--- a/SourceCode/Bond/Servo/CPageCollectionEvent.h
+++ b/SourceCode/Bond/Servo/CPageCollectionEvent.h
@@ -16,6 +16,8 @@
 
 private:
 	CListCtrlEx m_listCtrl;
+	void OnCreateBtns() override;
+	void OnClickedBtn(const char* btnName) override;
 
 // 瀵硅瘽妗嗘暟鎹�
 #ifdef AFX_DESIGN_TIME
@@ -31,4 +33,5 @@
 	afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
 	afx_msg void OnDestroy();
 	afx_msg void OnSize(UINT nType, int cx, int cy);
+	afx_msg void OnLvnItemchangedList1(NMHDR* pNMHDR, LRESULT* pResult);
 };
diff --git a/SourceCode/Bond/Servo/CPageCtrlState.cpp b/SourceCode/Bond/Servo/CPageCtrlState.cpp
new file mode 100644
index 0000000..de9972c
--- /dev/null
+++ b/SourceCode/Bond/Servo/CPageCtrlState.cpp
@@ -0,0 +1,157 @@
+锘�// CPageCtrlState.cpp: 瀹炵幇鏂囦欢
+//
+
+#include "stdafx.h"
+#include "Servo.h"
+#include "CPageCtrlState.h"
+#include "afxdialogex.h"
+#include "Common.h"
+#include "Model.h"
+#include "ColorTransfer.h"
+
+
+// CPageCtrlState 瀵硅瘽妗�
+
+IMPLEMENT_DYNAMIC(CPageCtrlState, CDialogEx)
+
+CPageCtrlState::CPageCtrlState(CWnd* pParent /*=nullptr*/)
+	: CDialogEx(IDD_PROD_CTRL_STATE, pParent)
+{
+
+}
+
+CPageCtrlState::~CPageCtrlState()
+{
+}
+
+void CPageCtrlState::DoDataExchange(CDataExchange* pDX)
+{
+	CDialogEx::DoDataExchange(pDX);
+	DDX_Control(pDX, IDC_BUTTON_OFFLINE, m_btnOffline);
+	DDX_Control(pDX, IDC_BUTTON_ONLINE_LOCAL, m_btnOnlineLocal);
+	DDX_Control(pDX, IDC_BUTTON_ONLINE_REMOTE, m_btnOnlineRemote);
+}
+
+
+BEGIN_MESSAGE_MAP(CPageCtrlState, CDialogEx)
+	ON_WM_CTLCOLOR()
+	ON_WM_SIZE()
+	ON_WM_DESTROY()
+	ON_BN_CLICKED(IDC_BUTTON_OFFLINE, &CPageCtrlState::OnBnClickedOffline)
+	ON_BN_CLICKED(IDC_BUTTON_ONLINE_LOCAL, &CPageCtrlState::OnBnClickedOnlineLocal)
+	ON_BN_CLICKED(IDC_BUTTON_ONLINE_REMOTE, &CPageCtrlState::OnBnClickedOnlineRemote)
+END_MESSAGE_MAP()
+
+
+// CPageCtrlState 娑堟伅澶勭悊绋嬪簭
+
+void CPageCtrlState::InitRxWindows()
+{
+	IRxWindows* pRxWindows = RX_GetRxWindows();
+	if (m_pObserver == nullptr) {
+		m_pObserver = pRxWindows->allocObserver([this](IAny* pAny) -> void {
+			pAny->addRef();
+			const int code = pAny->getCode();
+			if (code == RX_CODE_CONTROL_STATE_CHANGED && ::IsWindow(m_hWnd)) {
+				UpdateButtonStyles();
+			}
+			pAny->release();
+			}, [&]() -> void {
+				// onComplete
+			}, [&](IThrowable* pThrowable) -> void {
+				// onError
+				pThrowable->printf();
+			});
+
+		theApp.m_model.getObservable()->observeOn(pRxWindows->mainThread())
+			->subscribe(m_pObserver);
+	}
+}
+
+void CPageCtrlState::ApplyButtonTheme(CBlButton& btn, bool active)
+{
+	const COLORREF text = active ? RGB(255, 255, 255) : RGB(0, 0, 0);
+	const COLORREF normal = active ? RGB(34, 177, 76) : RGB(222, 222, 222);
+	const COLORREF hover = CColorTransfer::ApproximateColor(normal, active ? 0.08 : 0.05);
+	const COLORREF press = CColorTransfer::ApproximateColor(normal, active ? -0.10 : -0.12);
+	const COLORREF frame = active ? CColorTransfer::ApproximateColor(normal, -0.18) : RGB(168, 168, 168);
+
+	btn.SetRoundWidth(6);
+	btn.SetTextColor(BS_NORMAL, text);
+	btn.SetTextColor(BS_HOVER, text);
+	btn.SetTextColor(BS_PRESS, text);
+	btn.SetTextColor(BS_DISABLE, RGB(120, 120, 120));
+
+	btn.SetBkgndColor(BS_NORMAL, normal);
+	btn.SetBkgndColor(BS_HOVER, hover);
+	btn.SetBkgndColor(BS_PRESS, press);
+	btn.SetBkgndColor(BS_DISABLE, RGB(210, 210, 210));
+
+	btn.SetFrameColor(BS_NORMAL, frame);
+	btn.SetFrameColor(BS_HOVER, frame);
+	btn.SetFrameColor(BS_PRESS, frame);
+	btn.SetFrameColor(BS_DISABLE, RGB(180, 180, 180));
+}
+
+void CPageCtrlState::UpdateButtonStyles()
+{
+	const auto state = theApp.m_model.getControlState();
+	ApplyButtonTheme(m_btnOffline, state == ControlState::OfflineEquipment || state == ControlState::OfflineHost);
+	ApplyButtonTheme(m_btnOnlineLocal, state == ControlState::OnlineLocal);
+	ApplyButtonTheme(m_btnOnlineRemote, state == ControlState::OnlineRemote);
+
+	Invalidate();
+	UpdateWindow();
+}
+
+BOOL CPageCtrlState::OnInitDialog()
+{
+	CDialogEx::OnInitDialog();
+
+	// TODO:  鍦ㄦ娣诲姞棰濆鐨勫垵濮嬪寲
+	InitRxWindows();
+	UpdateButtonStyles();
+
+	return TRUE;  // return TRUE unless you set the focus to a control
+				  // 寮傚父: OCX 灞炴�ч〉搴旇繑鍥� FALSE
+}
+
+
+HBRUSH CPageCtrlState::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
+{
+	HBRUSH hbr = CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);
+
+	// TODO:  鍦ㄦ鏇存敼 DC 鐨勪换浣曠壒鎬�
+
+	// TODO:  濡傛灉榛樿鐨勪笉鏄墍闇�鐢荤瑪锛屽垯杩斿洖鍙︿竴涓敾绗�
+	return hbr;
+}
+
+void CPageCtrlState::OnSize(UINT nType, int cx, int cy)
+{
+	CDialogEx::OnSize(nType, cx, cy);
+
+	// TODO: 鍦ㄦ澶勬坊鍔犳秷鎭鐞嗙▼搴忎唬鐮�
+}
+
+void CPageCtrlState::OnDestroy()
+{
+	CDialogEx::OnDestroy();
+
+	// TODO: 鍦ㄦ澶勬坊鍔犳秷鎭鐞嗙▼搴忎唬鐮�
+}
+
+void CPageCtrlState::OnBnClickedOffline()
+{
+	theApp.m_model.setControlState(ControlState::OfflineEquipment);
+}
+
+void CPageCtrlState::OnBnClickedOnlineLocal()
+{
+	theApp.m_model.setControlState(ControlState::OnlineLocal);
+}
+
+void CPageCtrlState::OnBnClickedOnlineRemote()
+{
+	theApp.m_model.setControlState(ControlState::OnlineRemote);
+}
diff --git a/SourceCode/Bond/Servo/CPageCtrlState.h b/SourceCode/Bond/Servo/CPageCtrlState.h
new file mode 100644
index 0000000..2ed7343
--- /dev/null
+++ b/SourceCode/Bond/Servo/CPageCtrlState.h
@@ -0,0 +1,41 @@
+锘�#pragma once
+#include "BlButton.h"
+
+// CPageCtrlState 瀵硅瘽妗�
+
+class CPageCtrlState : public CDialogEx
+{
+	DECLARE_DYNAMIC(CPageCtrlState)
+
+public:
+	CPageCtrlState(CWnd* pParent = nullptr);   // 鏍囧噯鏋勯�犲嚱鏁�
+	virtual ~CPageCtrlState();
+
+private:
+	void InitRxWindows();
+	void UpdateButtonStyles();
+	void ApplyButtonTheme(CBlButton& btn, bool active);
+
+	CBlButton m_btnOffline;
+	CBlButton m_btnOnlineLocal;
+	CBlButton m_btnOnlineRemote;
+	IObserver* m_pObserver{ nullptr };
+
+// 瀵硅瘽妗嗘暟鎹�
+#ifdef AFX_DESIGN_TIME
+	enum { IDD = IDD_PROD_CTRL_STATE };
+#endif
+
+protected:
+	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 鏀寔
+
+	DECLARE_MESSAGE_MAP()
+public:
+	virtual BOOL OnInitDialog();
+	afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
+	afx_msg void OnSize(UINT nType, int cx, int cy);
+	afx_msg void OnDestroy();
+	afx_msg void OnBnClickedOffline();
+	afx_msg void OnBnClickedOnlineLocal();
+	afx_msg void OnBnClickedOnlineRemote();
+};
diff --git a/SourceCode/Bond/Servo/CPageDataVarialbles.cpp b/SourceCode/Bond/Servo/CPageDataVarialbles.cpp
new file mode 100644
index 0000000..cd68a1b
--- /dev/null
+++ b/SourceCode/Bond/Servo/CPageDataVarialbles.cpp
@@ -0,0 +1,220 @@
+锘�// CPageDataVarialbles.cpp: 瀹炵幇鏂囦欢
+//
+
+#include "stdafx.h"
+#include "Servo.h"
+#include "CPageDataVarialbles.h"
+#include "afxdialogex.h"
+#include "CVariableEditDlg2.h"
+
+IMPLEMENT_DYNAMIC(CPageDataVarialbles, CHMPropertyPage)
+
+CPageDataVarialbles::CPageDataVarialbles(CWnd* pParent /*=nullptr*/)
+	: CHMPropertyPage(IDD_PAGE_VARIABLE, pParent)
+{
+}
+
+CPageDataVarialbles::~CPageDataVarialbles()
+{
+}
+
+void CPageDataVarialbles::DoDataExchange(CDataExchange* pDX)
+{
+	CHMPropertyPage::DoDataExchange(pDX);
+	DDX_Control(pDX, IDC_LIST1, m_listCtrl);
+}
+
+BEGIN_MESSAGE_MAP(CPageDataVarialbles, CHMPropertyPage)
+	ON_WM_CTLCOLOR()
+	ON_WM_DESTROY()
+	ON_WM_SIZE()
+	ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST1, &CPageDataVarialbles::OnLvnItemchangedList1)
+END_MESSAGE_MAP()
+
+BOOL CPageDataVarialbles::OnInitDialog()
+{
+	CHMPropertyPage::OnInitDialog();
+
+	// 璇诲嚭鍒楀锛堢嫭绔嬬殑 ini 鍒嗚妭锛岄伩鍏嶄笌 SVID 椤甸潰鍐茬獊锛�
+	CString strIniFile, strItem;
+	strIniFile.Format(_T("%s\\configuration.ini"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir);
+	int width[8] = { 0, 218, 180, 180, 180, 180, 180, 180 };
+	for (int i = 0; i < 8; i++) {
+		strItem.Format(_T("Col_%d_Width"), i);
+		width[i] = GetPrivateProfileInt("PageDataVariableListCtrl", strItem, width[i], strIniFile);
+	}
+
+	DWORD dwStyle = m_listCtrl.GetExtendedStyle();
+	dwStyle |= LVS_EX_FULLROWSELECT;
+	dwStyle |= LVS_EX_GRIDLINES;
+	m_listCtrl.SetExtendedStyle(dwStyle);
+
+	HIMAGELIST imageList = ImageList_Create(24, 24, ILC_COLOR24, 1, 1);
+	ListView_SetImageList(m_listCtrl.GetSafeHwnd(), imageList, LVSIL_SMALL);
+	m_listCtrl.InsertColumn(0, _T(""), LVCFMT_RIGHT, width[0]);
+	m_listCtrl.InsertColumn(1, _T("DV ID"), LVCFMT_LEFT, width[1]);
+	m_listCtrl.InsertColumn(2, _T("DV Name"), LVCFMT_LEFT, width[2]);
+	m_listCtrl.InsertColumn(3, _T("DV Format"), LVCFMT_LEFT, width[3]);
+	m_listCtrl.InsertColumn(4, _T("DV Remark"), LVCFMT_LEFT, width[4]);
+
+	loadDataVariables();
+
+	return TRUE;
+}
+
+HBRUSH CPageDataVarialbles::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
+{
+	return CHMPropertyPage::OnCtlColor(pDC, pWnd, nCtlColor);
+}
+
+void CPageDataVarialbles::OnDestroy()
+{
+	CHMPropertyPage::OnDestroy();
+
+	// 淇濆瓨鍒楀
+	CString strIniFile, strItem, strTemp;
+	strIniFile.Format(_T("%s\\configuration.ini"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir);
+	CHeaderCtrl* pHeader = m_listCtrl.GetHeaderCtrl();
+	for (int i = 0; i < pHeader->GetItemCount(); i++) {
+		RECT rect;
+		pHeader->GetItemRect(i, &rect);
+		strItem.Format(_T("Col_%d_Width"), i);
+		strTemp.Format(_T("%d"), rect.right - rect.left);
+		WritePrivateProfileString("PageDataVariableListCtrl", strItem, strTemp, strIniFile);
+	}
+}
+
+void CPageDataVarialbles::OnSize(UINT nType, int cx, int cy)
+{
+	CHMPropertyPage::OnSize(nType, cx, cy);
+	if (GetDlgItem(IDC_LIST1) == nullptr) return;
+
+	CRect rcClient;
+	GetClientRect(&rcClient);
+	m_listCtrl.MoveWindow(12, 12, rcClient.Width() - 24, rcClient.Height() - 24);
+}
+
+void CPageDataVarialbles::OnApply()
+{
+	__super::OnApply();
+}
+
+void CPageDataVarialbles::loadDataVariables()
+{
+	auto& dvars = theApp.m_model.m_hsmsPassive.getDataVariables();
+	for (auto item : dvars) {
+		int index = m_listCtrl.InsertItem(m_listCtrl.GetItemCount(), _T(""));
+		m_listCtrl.SetItemData(index, (DWORD_PTR)item);
+		m_listCtrl.SetItemText(index, 1, std::to_string(item->getVarialbleId()).c_str());
+		m_listCtrl.SetItemText(index, 2, item->getName().c_str());
+		m_listCtrl.SetItemText(index, 3, SERVO::CVariable::formatToString(item->getFormat()).c_str());
+		m_listCtrl.SetItemText(index, 4, item->getRemark().c_str());
+	}
+}
+
+void CPageDataVarialbles::OnCreateBtns()
+{
+	const int BTN_W = 80;
+	const int BTN_H = 28;
+	CreateBtn(_T("鏂板"), BTN_W, BTN_H, 1001);
+	CreateBtn(_T("鍒犻櫎"), BTN_W, BTN_H, 1002)->EnableWindow(FALSE);
+	CreateBtn(_T("缂栬緫"), BTN_W, BTN_H, 1003)->EnableWindow(FALSE);
+}
+
+void CPageDataVarialbles::OnLvnItemchangedList1(NMHDR* pNMHDR, LRESULT* pResult)
+{
+	LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
+	int nSelCount = m_listCtrl.GetSelectedCount();
+
+	if (CButton* pDel = GetBtnByName("鍒犻櫎")) {
+		pDel->EnableWindow(nSelCount > 0);
+	}
+	if (CButton* pEdit = GetBtnByName("缂栬緫")) {
+		pEdit->EnableWindow(nSelCount > 0);
+	}
+
+	*pResult = 0;
+}
+
+void CPageDataVarialbles::OnClickedBtn(const char* btnName)
+{
+	ASSERT(btnName);
+	if (_strcmpi(btnName, "鏂板") == 0) {
+		int rc = UX_CanExecute(L"addVarialbles");
+		if (rc != 1) {
+			AfxMessageBox("鎿嶄綔鏉冮檺涓嶈冻锛岃鑱旂郴绠$悊浜哄憳锛�");
+			return;
+		}
+		unsigned int newId = theApp.m_model.m_hsmsPassive.getMaxDataVariableId();
+		int newIdInt = static_cast<int>(newId + 1);
+		CVariableEditDlg2 dlg(_T("鏂板鏁版嵁鍙橀噺"), newIdInt, _T("U1"), _T(""), _T(""), this);
+		if (dlg.DoModal() != IDOK) return;
+		CString name = dlg.GetNameText();
+		CString fmt = dlg.GetTypeText();
+		CString remark = dlg.GetRemark();
+
+		int ret = theApp.m_model.m_hsmsPassive.addDataVariable(CT2A(name), CT2A(fmt), CT2A(remark), newIdInt);
+		if (ret == 0) {
+			UX_RecordAction(L"addVarialbles");
+			m_listCtrl.DeleteAllItems();
+			loadDataVariables();
+		}
+		else {
+			AfxMessageBox(_T("鏂板鏁版嵁鍙橀噺澶辫触锛屾牸寮忔槸鍚︽纭紵(U1/U2/I2/A20/A50/L)"));
+		}
+	}
+	else if (_strcmpi(btnName, "鍒犻櫎") == 0) {
+		POSITION pos = m_listCtrl.GetFirstSelectedItemPosition();
+		if (pos == nullptr) return;
+		int nItem = m_listCtrl.GetNextSelectedItem(pos);
+		auto pVar = reinterpret_cast<SERVO::CDataVariable*>(m_listCtrl.GetItemData(nItem));
+		if (pVar == nullptr) return;
+
+		int rc = UX_CanExecute(L"delVarialbles");
+		if (rc != 1) {
+			AfxMessageBox("鎿嶄綔鏉冮檺涓嶈冻锛岃鑱旂郴绠$悊浜哄憳锛�");
+			return;
+		}
+		int ret = theApp.m_model.m_hsmsPassive.deleteDataVariable(static_cast<int>(pVar->getVarialbleId()));
+		if (ret == 0) {
+			UX_RecordAction(L"delVarialbles");
+			m_listCtrl.DeleteAllItems();
+			loadDataVariables();
+			if (CButton* pDel = GetBtnByName("鍒犻櫎")) pDel->EnableWindow(FALSE);
+			if (CButton* pEdit = GetBtnByName("缂栬緫")) pEdit->EnableWindow(FALSE);
+		}
+	}
+	else if (_strcmpi(btnName, "缂栬緫") == 0) {
+		POSITION pos = m_listCtrl.GetFirstSelectedItemPosition();
+		if (pos == nullptr) return;
+		int nItem = m_listCtrl.GetNextSelectedItem(pos);
+		auto pVar = reinterpret_cast<SERVO::CDataVariable*>(m_listCtrl.GetItemData(nItem));
+		if (pVar == nullptr) return;
+
+		int rc = UX_CanExecute(L"editVarialbles");
+		if (rc != 1) {
+			AfxMessageBox("鎿嶄綔鏉冮檺涓嶈冻锛岃鑱旂郴绠$悊浜哄憳锛�");
+			return;
+		}
+		CVariableEditDlg2 dlg(_T("缂栬緫鏁版嵁鍙橀噺"),
+			pVar->getVarialbleId(),
+			CString(CA2T(SERVO::CVariable::formatToString(pVar->getFormat()).c_str())),
+			CString(CA2T(pVar->getName().c_str())),
+			CString(CA2T(pVar->getRemark().c_str())),
+			this);
+		if (dlg.DoModal() != IDOK) return;
+		CString name = dlg.GetNameText();
+		CString fmt = dlg.GetTypeText();
+		CString remark = dlg.GetRemark();
+
+		int ret = theApp.m_model.m_hsmsPassive.updateDataVariable(static_cast<int>(pVar->getVarialbleId()), CT2A(name), CT2A(fmt), CT2A(remark));
+		if (ret == 0) {
+			UX_RecordAction(L"editVarialbles");
+			m_listCtrl.DeleteAllItems();
+			loadDataVariables();
+		}
+		else {
+			AfxMessageBox(_T("缂栬緫鏁版嵁鍙橀噺澶辫触锛屾牸寮忔槸鍚︽纭紵(U1/U2/I2/A20/A50/L)"));
+		}
+	}
+}
diff --git a/SourceCode/Bond/Servo/CPageDataVarialbles.h b/SourceCode/Bond/Servo/CPageDataVarialbles.h
new file mode 100644
index 0000000..9abcac4
--- /dev/null
+++ b/SourceCode/Bond/Servo/CPageDataVarialbles.h
@@ -0,0 +1,37 @@
+锘�#pragma once
+#include "CHMPropertyPage.h"
+#include "ListCtrlEx.h"
+
+// CPageDataVarialbles 瀵硅瘽妗嗭紙DVID 缂栬緫/鏌ョ湅锛�
+class CPageDataVarialbles : public CHMPropertyPage
+{
+	DECLARE_DYNAMIC(CPageDataVarialbles)
+
+public:
+	CPageDataVarialbles(CWnd* pParent = nullptr);
+	virtual ~CPageDataVarialbles();
+	virtual void OnApply();
+	void loadDataVariables();
+	virtual void OnCreateBtns();
+
+private:
+	CListCtrlEx m_listCtrl;
+
+protected:
+	virtual void OnClickedBtn(const char* btnName) override;
+
+#ifdef AFX_DESIGN_TIME
+	enum { IDD = IDD_PAGE_VARIABLE };
+#endif
+
+protected:
+	virtual void DoDataExchange(CDataExchange* pDX);
+
+	DECLARE_MESSAGE_MAP()
+public:
+	virtual BOOL OnInitDialog();
+	afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
+	afx_msg void OnDestroy();
+	afx_msg void OnSize(UINT nType, int cx, int cy);
+	afx_msg void OnLvnItemchangedList1(NMHDR* pNMHDR, LRESULT* pResult);
+};
diff --git a/SourceCode/Bond/Servo/CPageGlassList.cpp b/SourceCode/Bond/Servo/CPageGlassList.cpp
index cbe79ed..29b3b23 100644
--- a/SourceCode/Bond/Servo/CPageGlassList.cpp
+++ b/SourceCode/Bond/Servo/CPageGlassList.cpp
@@ -13,6 +13,7 @@
 #include <unordered_map>
 #include <vector>
 #include <string>
+#include <algorithm>
 #include "CProcessDataListDlg.h"
 
 #define PAGE_SIZE                       50
@@ -1087,9 +1088,10 @@
 {
     CDialogEx::OnInitDialog();
 
-    // 瀹氭椂鍣細1=鍒濆鍖栬闃咃紝2=鍛ㄦ湡鍒锋柊锛堝彧澧為噺锛�
+    // 瀹氭椂鍣細1=鍒濆鍖栬闃咃紝2=鍛ㄦ湡鍒锋柊锛堝彧澧為噺锛夛紝3=寤惰繜鍔犺浇棣栧睆鏁版嵁
     SetTimer(1, 3000, nullptr);
     SetTimer(2, 2000, nullptr);
+    SetTimer(3, 10, nullptr);
 
     // 涓嬫媺妗嗘帶浠�
     InitStatusCombo();
@@ -1139,7 +1141,6 @@
     m_listCtrl.SetPopupFullTextColumns({ 11, 12 });
 
     Resize();
-    OnBnClickedButtonSearch(); // 瑙﹀彂涓�娆℃煡璇笌棣栧睆濉厖
 
     return TRUE;  // return TRUE unless you set the focus to a control
 }
@@ -1199,6 +1200,10 @@
     else if (nIDEvent == 2) {
         UpdateWipData();  // 鍙仛澧為噺锛屼笉閲嶅缓
     }
+    else if (nIDEvent == 3) {
+        KillTimer(3);
+        OnBnClickedButtonSearch(); // 寤惰繜棣栧睆鏌ヨ锛岄伩鍏嶅崱浣� OnInitDialog
+    }
 
     CDialogEx::OnTimer(nIDEvent);
 }
@@ -1228,6 +1233,8 @@
 
 void CPageGlassList::OnBnClickedButtonSearch()
 {
+    CWaitCursor wait; // 鏄剧ず绛夊緟鍏夋爣锛屾彁绀烘鍦ㄥ姞杞�
+
     // 鑾峰彇鍏抽敭瀛楄緭鍏ユ鍐呭
     CString strKeyword;
     GetDlgItemText(IDC_EDIT_KEYWORD, strKeyword);
@@ -1363,7 +1370,7 @@
     if (!row.pretty.empty()) {
         CFile file;
         if (file.Open(filePath, CFile::modeCreate | CFile::modeWrite)) {
-            file.Write(row.pretty.c_str(), row.pretty.length());
+            file.Write(row.pretty.c_str(), (UINT)row.pretty.length());
             file.Close();
 
             CString strSuccess;
@@ -1481,69 +1488,59 @@
             // 瀵规瘡涓満鍣ㄧ敓鎴愯〃鏍�
             for (const auto& machinePair : tempGlass.getAllSVData()) {
                 int machineId = machinePair.first;
+                const auto& dataByType = machinePair.second;
                 CString machineName = CString(SERVO::CServoUtilsTool::getEqName(machineId).c_str());
 
                 csvContent += _T("\n[") + machineName + _T("]\n");
 
-                // 鑾峰彇璇ユ満鍣ㄧ殑棰勫畾涔夊垪椤哄簭
-                auto columnOrder = getMachineColumnOrder(machineId);
-
-                if (columnOrder.empty()) {
-                    csvContent += _T("鏃犻瀹氫箟鍒楅厤缃甛n");
+                if (dataByType.empty()) {
+                    csvContent += _T("No sensor data\n");
                     continue;
                 }
 
-                // 鏋勫缓琛ㄥご - 鐩存帴浣跨敤涓枃鍒楀悕
-                CString header = _T("鏃堕棿鎴�(ms),鏈湴鏃堕棿");
+                auto columnOrder = getMachineColumnOrder(machineId, &dataByType);
+                if (columnOrder.empty()) {
+                    csvContent += _T("No exportable columns\n");
+                    continue;
+                }
+
+                CString header = _T("Timestamp(ms),LocalTime");
                 for (const auto& dataType : columnOrder) {
                     header += _T(",");
-                    header += CString(dataType.c_str()); // 鐩存帴浣跨敤涓枃鍒楀悕
+                    header += CString(dataType.c_str());
                 }
                 header += _T("\n");
                 csvContent += header;
 
-                // 妫�鏌ユ槸鍚︽湁鏁版嵁
-                if (machinePair.second.empty()) {
-                    csvContent += _T("鏃犱紶鎰熷櫒鏁版嵁\n");
+                auto baselineIt = std::find_if(columnOrder.begin(), columnOrder.end(),
+                    [&](const std::string& type) {
+                        auto dataIt = dataByType.find(type);
+                        return dataIt != dataByType.end() && !dataIt->second.empty();
+                    });
+                if (baselineIt == columnOrder.end()) {
+                    csvContent += _T("No usable time series\n");
                     continue;
                 }
 
-                // 浣跨敤绗竴涓暟鎹被鍨嬬殑鏃堕棿搴忓垪浣滀负鍩哄噯
-                const std::string& firstDataType = columnOrder[0];
-                auto firstDataTypeIt = machinePair.second.find(firstDataType);
-                if (firstDataTypeIt == machinePair.second.end() || firstDataTypeIt->second.empty()) {
-                    csvContent += _T("鏃犲熀鍑嗘暟鎹被鍨嬫暟鎹甛n");
-                    continue;
-                }
-
-                const auto& timeSeries = firstDataTypeIt->second;
-
-                // 瀵逛簬姣忎釜鏃堕棿鐐癸紝杈撳嚭涓�琛屾暟鎹�
-                for (size_t i = 0; i < timeSeries.size(); i++) {
+                const auto& timeSeries = dataByType.at(*baselineIt);
+                for (size_t i = 0; i < timeSeries.size(); ++i) {
                     auto timestamp = timeSeries[i].timestamp;
-
-                    // 鏃堕棿鎴筹紙姣锛�
                     auto ms = timePointToMs(timestamp);
                     CString row;
                     row.Format(_T("%lld,"), ms);
 
-                    // 鏈湴鏃堕棿瀛楃涓�
                     CString localTime = CString(timePointToString(timestamp).c_str());
                     row += localTime;
 
-                    // 鎸夌収棰勫畾涔夌殑鍒楅『搴忚緭鍑烘暟鎹�
                     for (const auto& dataType : columnOrder) {
                         row += _T(",");
-
-                        auto dataTypeIt = machinePair.second.find(dataType);
-                        if (dataTypeIt != machinePair.second.end() && i < dataTypeIt->second.size()) {
-                            // 鐩存帴鎸夌储寮曡幏鍙栨暟鎹�
+                        auto dataTypeIt = dataByType.find(dataType);
+                        if (dataTypeIt != dataByType.end() && i < dataTypeIt->second.size()) {
                             CString valueStr;
                             valueStr.Format(_T("%.3f"), dataTypeIt->second[i].value);
                             row += valueStr;
                         }
                         else {
-                            // 鐞嗚涓婁笉搴旇鍙戠敓锛屽洜涓烘偍璇存病鏈夌┖鍊�
                             row += _T("N/A");
                         }
                     }
@@ -1582,9 +1579,14 @@
     auto* p = reinterpret_cast<NMC_ELC_SHOWFULLTEXT*>(pNMHDR);
 
     // 瀵硅瘽妗嗘樉绀哄伐鑹哄弬鏁�
-    CProcessDataListDlg dlg;
-    dlg.setRawText(p->text);
-    dlg.DoModal();
+    if (p->iSubItem == 12) {
+        CProcessDataListDlg dlg;
+        dlg.setRawText(p->text);
+        dlg.DoModal();
+    }
+    else {
+        AfxMessageBox(p->text);
+    }
 
     *pResult = 0;
 }
@@ -1919,11 +1921,33 @@
 }
 
 // 鑾峰彇鏈哄櫒棰勫畾涔夌殑鍒楅『搴�
-std::vector<std::string> CPageGlassList::getMachineColumnOrder(int machineId)
+std::vector<std::string> CPageGlassList::getMachineColumnOrder(int machineId,
+    const std::unordered_map<std::string, std::vector<SERVO::SVDataItem>>* actualData)
 {
+    std::vector<std::string> columnOrder;
     auto dataTypes = SERVO::CServoUtilsTool::getEqDataTypes();
     auto it = dataTypes.find(machineId);
-    return it != dataTypes.end() ? it->second : std::vector<std::string>();
+
+    if (actualData != nullptr) {
+        if (it != dataTypes.end()) {
+            for (const auto& name : it->second) {
+                if (actualData->find(name) != actualData->end()) {
+                    columnOrder.push_back(name);
+                }
+            }
+        }
+        for (const auto& kv : *actualData) {
+            if (std::find(columnOrder.begin(), columnOrder.end(), kv.first) == columnOrder.end()) {
+                columnOrder.push_back(kv.first);
+            }
+        }
+        return columnOrder;
+    }
+
+    if (it != dataTypes.end()) {
+        columnOrder = it->second;
+    }
+    return columnOrder;
 }
 
 // 鏃堕棿鎴宠浆鎹负瀛楃涓�
@@ -1953,13 +1977,25 @@
     for (const auto& machinePair : dataTypes) {
         int machineId = machinePair.first;
         const auto& dataTypeList = machinePair.second;
+        std::vector<std::string> filteredTypes;
+
+        if (machineId == EQ_ID_VACUUMBAKE || machineId == EQ_ID_BAKE_COOLING) {
+            const char activePrefix = 'A';
+            for (const auto& dataType : dataTypeList) {
+                if (!dataType.empty() && dataType[0] == activePrefix) {
+                    filteredTypes.push_back(dataType);
+                }
+            }
+        }
+
+        const auto& typeList = filteredTypes.empty() ? dataTypeList : filteredTypes;
         
         // 鐢熸垚鏃堕棿搴忓垪锛氫粠褰撳墠鏃堕棿寰�鍓嶆帹10鍒嗛挓锛屾瘡1绉掍竴涓暟鎹偣
         auto now = std::chrono::system_clock::now();
         auto startTime = now - std::chrono::minutes(10);
         
         // 涓烘瘡涓暟鎹被鍨嬬敓鎴愭ā鎷熸暟鎹�
-        for (const auto& dataType : dataTypeList) {
+        for (const auto& dataType : typeList) {
             std::vector<SERVO::SVDataItem> mockData;
             
             // 鐢熸垚600涓暟鎹偣锛�10鍒嗛挓 * 60涓偣/鍒嗛挓锛�
@@ -2029,4 +2065,4 @@
     double randomNoise = (rand() % 100 - 50) / 100.0 * variation * 0.3;  // 闅忔満鍣0
     
     return baseValue + timeTrend + randomNoise;
-}
\ No newline at end of file
+}
diff --git a/SourceCode/Bond/Servo/CPageGlassList.h b/SourceCode/Bond/Servo/CPageGlassList.h
index 09e548a..38a35c7 100644
--- a/SourceCode/Bond/Servo/CPageGlassList.h
+++ b/SourceCode/Bond/Servo/CPageGlassList.h
@@ -1,6 +1,7 @@
 锘�#pragma once
 #include "CExpandableListCtrl.h"
 #include "GlassLogDb.h"
+#include <unordered_map>
 
 // ====== 缂栬瘧寮�鍏宠鏄� ======
 // USE_MOCK_SENSOR_DATA: 1=鍚敤妯℃嫙浼犳劅鍣ㄦ暟鎹敓鎴愶紱0=浣跨敤鐪熷疄鏁版嵁
@@ -65,7 +66,7 @@
 	void ExportBasicInfo(CString& csvContent, const GlassLogDb::Row& row);
 	void ExportProcessParams(CString& csvContent, const GlassLogDb::Row& row);
 	void ExportSensorData(CString& csvContent, const GlassLogDb::Row& row);
-	static std::vector<std::string> getMachineColumnOrder(int machineId);
+	static std::vector<std::string> getMachineColumnOrder(int machineId, const std::unordered_map<std::string, std::vector<SERVO::SVDataItem>>* actualData = nullptr);
 	static std::string timePointToString(const std::chrono::system_clock::time_point& tp);
 	static int64_t timePointToMs(const std::chrono::system_clock::time_point& tp);
 	void GenerateMockSVData(SERVO::CGlass& glass);
diff --git a/SourceCode/Bond/Servo/CPageGraph1.cpp b/SourceCode/Bond/Servo/CPageGraph1.cpp
index 60f2ac4..2ae613e 100644
--- a/SourceCode/Bond/Servo/CPageGraph1.cpp
+++ b/SourceCode/Bond/Servo/CPageGraph1.cpp
@@ -180,7 +180,7 @@
 {
 	CDialogEx::OnInitDialog();
 	InitRxWindows();
-	SetTimer(TIMER_ID_DEVICE_STATUS, 3000, nullptr);
+	SetTimer(TIMER_ID_DEVICE_STATUS, 800, nullptr);
 	SetTimer(TIMER_ID_ROBOT_STATUS, 1000, nullptr); // 姣� 1000ms 鏇存柊涓�娆$姸鎬�
 
 	// 鍥剧ず
@@ -196,73 +196,73 @@
 	// Bonder
 	m_pGraph->AddIndicateBox(INDICATE_BONDER1, 220, 172, 48, RGB(22, 22, 22),
 		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_BONDER1, "10", "Bonder 1");
+	m_pGraph->SetBoxText(INDICATE_BONDER1, "", "Bonder 1");
 	m_pGraph->AddIndicateBox(INDICATE_BONDER2, 220, 516, 48, RGB(22, 22, 22),
 		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_BONDER2, "11", "Bonder 2");
+	m_pGraph->SetBoxText(INDICATE_BONDER2, "", "Bonder 2");
 
 
 	// 缈昏浆
 	m_pGraph->AddIndicateBox(INDICATE_FLIPER, 338, 172, 48, RGB(22, 22, 22),
 		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_FLIPER, "8", "Fliper");
+	m_pGraph->SetBoxText(INDICATE_FLIPER, "", "Fliper");
 
 
 	// 瀵逛綅
 	m_pGraph->AddIndicateBox(INDICATE_ALIGNER, 428, 172, 48, RGB(22, 22, 22),
 		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_ALIGNER, "7", "Aligner");
+	m_pGraph->SetBoxText(INDICATE_ALIGNER, "", "Aligner");
 
 
 	// Load port 4
 	m_pGraph->AddIndicateBox(INDICATE_LPORT4, 518, 172, 48, RGB(22, 22, 22),
 		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_LPORT4, "4", "LPort4");
+	m_pGraph->SetBoxText(INDICATE_LPORT4, "", "LPort4");
 
 
 	// Load port 3
 	m_pGraph->AddIndicateBox(INDICATE_LPORT3, 606, 172, 48, RGB(22, 22, 22),
 		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_LPORT3, "3", "LPort3");
+	m_pGraph->SetBoxText(INDICATE_LPORT3, "", "LPort3");
 
 
 	// Load port 2
 	m_pGraph->AddIndicateBox(INDICATE_LPORT2, 690, 172, 48, RGB(22, 22, 22),
 		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_LPORT2, "2", "LPort2");
+	m_pGraph->SetBoxText(INDICATE_LPORT2, "", "LPort2");
 
 
 	// Load port 1
 	m_pGraph->AddIndicateBox(INDICATE_LPORT1, 774, 172, 48, RGB(22, 22, 22),
 		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_LPORT1, "1", "LPort1");
+	m_pGraph->SetBoxText(INDICATE_LPORT1, "", "LPort1");
 
 
 	// Robot
 	m_pGraph->AddIndicateBox(INDICATE_ROBOT_ARM1, 190, 294, 48, RGB(22, 22, 22),
 		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_ROBOT_ARM1, "5", "Robot");
+	m_pGraph->SetBoxText(INDICATE_ROBOT_ARM1, "", "Robot");
 	m_pGraph->AddIndicateBox(INDICATE_ROBOT_ARM2, 243, 294, 48, RGB(22, 22, 22),
 		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_ROBOT_ARM2, "6", "Robot");
+	m_pGraph->SetBoxText(INDICATE_ROBOT_ARM2, "", "Robot");
 
 
 	// Vacuum bake
 	m_pGraph->AddIndicateBox(INDICATE_VACUUM_BAKE, 396, 516, 48, RGB(22, 22, 22),
 		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_VACUUM_BAKE, "9", "Vacuum bake");
+	m_pGraph->SetBoxText(INDICATE_VACUUM_BAKE, "", "Vacuum bake");
 
 
 	// Bake cooling
 	m_pGraph->AddIndicateBox(INDICATE_BAKE_COOLING, 566, 516, 48, RGB(22, 22, 22),
 		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_BAKE_COOLING, "12", "Bake cooling");
+	m_pGraph->SetBoxText(INDICATE_BAKE_COOLING, "", "Bake cooling");
 
 
 	// 绮惧害妫�
 	m_pGraph->AddIndicateBox(INDICATE_MEASUREMENT, 737, 516, 48, RGB(22, 22, 22),
 		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_MEASUREMENT, "13", "Measurement");
+	m_pGraph->SetBoxText(INDICATE_MEASUREMENT, "", "Measurement");
 
 
 	return TRUE;  // return TRUE unless you set the focus to a control
@@ -560,37 +560,7 @@
 
 	// 绉诲姩鍒版寚瀹氫綅缃� (娴嬭瘯浣跨敤)
 	if (pGraphNmhdr->dwData == INDICATE_LPORT1) {
-		StartRobotMoveToPosition(SERVO::ROBOT_POSITION::Port1);
-	}
-	else if (pGraphNmhdr->dwData == INDICATE_LPORT2) {
-		StartRobotMoveToPosition(SERVO::ROBOT_POSITION::Port2);
-	}
-	else if (pGraphNmhdr->dwData == INDICATE_LPORT3) {
-		StartRobotMoveToPosition(SERVO::ROBOT_POSITION::Port3);
-	}
-	else if (pGraphNmhdr->dwData == INDICATE_LPORT4) {
-		StartRobotMoveToPosition(SERVO::ROBOT_POSITION::Port4);
-	}
-	else if (pGraphNmhdr->dwData == INDICATE_ALIGNER) {
-		StartRobotMoveToPosition(SERVO::ROBOT_POSITION::Aligner);
-	}
-	else if (pGraphNmhdr->dwData == INDICATE_FLIPER) {
-		StartRobotMoveToPosition(SERVO::ROBOT_POSITION::Fliper);
-	}
-	else if (pGraphNmhdr->dwData == INDICATE_BONDER1) {
-		StartRobotMoveToPosition(SERVO::ROBOT_POSITION::Bonder1);
-	}
-	else if (pGraphNmhdr->dwData == INDICATE_BONDER2) {
-		StartRobotMoveToPosition(SERVO::ROBOT_POSITION::Bonder2);
-	}
-	else if (pGraphNmhdr->dwData == INDICATE_VACUUM_BAKE) {
-		StartRobotMoveToPosition(SERVO::ROBOT_POSITION::Bake);
-	}
-	else if (pGraphNmhdr->dwData == INDICATE_BAKE_COOLING) {
-		StartRobotMoveToPosition(SERVO::ROBOT_POSITION::Cooling);
-	}
-	else if (pGraphNmhdr->dwData == INDICATE_MEASUREMENT) {
-		StartRobotMoveToPosition(SERVO::ROBOT_POSITION::Measurement);
+
 	}
 	
 	*pResult = 0;
diff --git a/SourceCode/Bond/Servo/CPageGraph2.cpp b/SourceCode/Bond/Servo/CPageGraph2.cpp
index cb4d449..1156425 100644
--- a/SourceCode/Bond/Servo/CPageGraph2.cpp
+++ b/SourceCode/Bond/Servo/CPageGraph2.cpp
@@ -64,10 +64,8 @@
 			if (RX_CODE_EQ_DATA_CHANGED == code) {
 				// 閫氱煡璁惧鐘舵��
 				SERVO::CEquipment* pEquipment = nullptr;
-				if (pAny->getPtrValue("ptr", (void*&)pEquipment)) {
-					if (pEquipment != nullptr) {
-						m_pEqsGraphWnd->ShowItemIndicator((DWORD_PTR)pEquipment, pEquipment->hasGlass());
-					}
+				if (pAny->getPtrValue("ptr", (void*&)pEquipment) && pEquipment != nullptr) {
+					UpdateItemIndicators(pEquipment);
 				}
 			}
 
@@ -305,6 +303,12 @@
 	m_pEqsGraphWnd->SetBkgndColor(m_crBkgnd);
 	m_pEqsGraphWnd->SetOnListener(listener);
 
+	CString strIniFile, strItem;
+	strIniFile.Format(_T("%s\\configuration.ini"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir);
+	int nIndicatorSize = GetPrivateProfileInt("PageGraph2", _T("IndicatorSize"), 10, strIniFile);
+	int nIndicatorMargin = GetPrivateProfileInt("PageGraph2", _T("IndicatorMargin"), 0, strIniFile);
+	m_pEqsGraphWnd->SetIndicatorSize(nIndicatorSize);
+	m_pEqsGraphWnd->SetIndicatorMargin(nIndicatorMargin);
 
 	return TRUE;  // return TRUE unless you set the focus to a control
 				  // 寮傚父: OCX 灞炴�ч〉搴旇繑鍥� FALSE
@@ -377,7 +381,20 @@
 		m_pEqsGraphWnd->AddPin(pItem, OUTPIN, outPin->getName().c_str(), (DWORD_PTR)outPin);
 	}
 
-	m_pEqsGraphWnd->ShowItemIndicator((DWORD_PTR)pEquipment, pEquipment->hasGlass());
+	UpdateItemIndicators(pEquipment);
+}
+
+void CPageGraph2::UpdateItemIndicators(SERVO::CEquipment* pEquipment)
+{
+	for (int i = 0; i < SLOT_MAX; i++) {
+		auto pSlot = pEquipment->getSlot(i);
+
+		int state = 0;
+		if (pSlot->isEnable()) {
+			state = pSlot->getContext() != nullptr ? 1 : 2;
+		}
+		m_pEqsGraphWnd->ShowItemIndicator((DWORD_PTR)pEquipment, state, i);
+	}
 }
 
 void CPageGraph2::OnTimer(UINT_PTR nIDEvent)
diff --git a/SourceCode/Bond/Servo/CPageGraph2.h b/SourceCode/Bond/Servo/CPageGraph2.h
index 736fb65..3c3e5e5 100644
--- a/SourceCode/Bond/Servo/CPageGraph2.h
+++ b/SourceCode/Bond/Servo/CPageGraph2.h
@@ -16,6 +16,7 @@
 private:
 	void InitRxWindows();
 	void AddEqToGraphWnd(SERVO::CEquipment* pEquipment);
+	void UpdateItemIndicators(SERVO::CEquipment* pEquipment);
 	void SaveEqsGraphData();
 	void GetItemDataFormIni(const char* pszItemName, int& left, int& top);
 
diff --git a/SourceCode/Bond/Servo/CPageProdOverview.cpp b/SourceCode/Bond/Servo/CPageProdOverview.cpp
new file mode 100644
index 0000000..11813f4
--- /dev/null
+++ b/SourceCode/Bond/Servo/CPageProdOverview.cpp
@@ -0,0 +1,165 @@
+锘�// CPageProOverview.cpp: 瀹炵幇鏂囦欢
+//
+
+#include "stdafx.h"
+#include "Servo.h"
+#include "CPageProdOverview.h"
+#include "afxdialogex.h"
+#include "CPanelProduction.h"
+
+namespace
+{
+	constexpr UINT_PTR kTimerRefreshId = 2001;
+	constexpr UINT kTimerRefreshIntervalMs = 10000;
+}
+
+IMPLEMENT_DYNAMIC(CPageProdOverview, CDialogEx)
+
+CPageProdOverview::CPageProdOverview(CWnd* pParent /*=nullptr*/)
+	: CDialogEx(IDD_PROD_OVERVIEW, pParent)
+	, m_clrBackground(RGB(240, 240, 240))
+{
+}
+
+CPageProdOverview::~CPageProdOverview()
+{
+}
+
+void CPageProdOverview::DoDataExchange(CDataExchange* pDX)
+{
+	CDialogEx::DoDataExchange(pDX);
+}
+
+void CPageProdOverview::SetBackgroundColor(COLORREF color)
+{
+	m_clrBackground = color;
+	m_brushBackground.DeleteObject();
+	m_brushBackground.CreateSolidBrush(m_clrBackground);
+	if (::IsWindow(m_hWnd)) {
+		Invalidate();
+	}
+}
+
+BEGIN_MESSAGE_MAP(CPageProdOverview, CDialogEx)
+	ON_WM_CTLCOLOR()
+	ON_WM_DESTROY()
+	ON_WM_SIZE()
+	ON_WM_TIMER()
+END_MESSAGE_MAP()
+
+BOOL CPageProdOverview::OnInitDialog()
+{
+	CDialogEx::OnInitDialog();
+
+
+	// 浣跨敤鑷畾涔夋爣绛�
+	if (CWnd* pDay = GetDlgItem(IDC_PROD_DAY_OUTPUT)) {
+		m_labelDayOut.SubclassWindow(pDay->GetSafeHwnd());
+		m_labelDayOut.setFontSize(28);
+		m_labelDayOut.setNoteTextColor(RGB(128, 128, 128));
+		m_labelDayOut.setNote1(_T("鐧界彮浜у嚭"));
+		m_labelDayOut.setBackground(m_clrBackground);
+		m_labelDayOut.setForeground(RGB(18, 18, 18), TRUE);
+	}
+	if (CWnd* pNight = GetDlgItem(IDC_PROD_NIGHT_OUTPUT)) {
+		m_labelNightOut.SubclassWindow(pNight->GetSafeHwnd());
+		m_labelNightOut.setFontSize(28);
+		m_labelNightOut.setNoteTextColor(RGB(128, 128, 128));
+		m_labelNightOut.setNote1(_T("澶滅彮浜у嚭"));
+		m_labelNightOut.setBackground(m_clrBackground);
+		m_labelNightOut.setForeground(RGB(18, 18, 18), TRUE);
+	}
+	if (CWnd* pDayTakt = GetDlgItem(IDC_PROD_DAY_TAKT)) {
+		m_labelDayTakt.SubclassWindow(pDayTakt->GetSafeHwnd());
+		m_labelDayTakt.setFontSize(28);
+		m_labelDayTakt.setNoteTextColor(RGB(128, 128, 128));
+		m_labelDayTakt.setNote1(_T("鐧界彮骞冲潎TT"));
+		m_labelDayTakt.setBackground(m_clrBackground);
+		m_labelDayTakt.setForeground(RGB(18, 18, 18), TRUE);
+	}
+	if (CWnd* pNightTakt = GetDlgItem(IDC_PROD_NIGHT_TAKT)) {
+		m_labelNightTakt.SubclassWindow(pNightTakt->GetSafeHwnd());
+		m_labelNightTakt.setFontSize(28);
+		m_labelNightTakt.setNoteTextColor(RGB(128, 128, 128));
+		m_labelNightTakt.setNote1(_T("澶滅彮骞冲潎TT"));
+		m_labelNightTakt.setBackground(m_clrBackground);
+		m_labelNightTakt.setForeground(RGB(18, 18, 18), TRUE);
+	}
+
+	RefreshData();
+	m_timerId = SetTimer(kTimerRefreshId, kTimerRefreshIntervalMs, nullptr);
+	return TRUE;
+}
+
+HBRUSH CPageProdOverview::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
+{
+	HBRUSH hbr = CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);
+
+	if (nCtlColor == CTLCOLOR_DLG || nCtlColor == CTLCOLOR_STATIC) {
+		if (m_brushBackground.GetSafeHandle() == NULL) {
+			m_brushBackground.CreateSolidBrush(m_clrBackground);
+		}
+		pDC->SetBkMode(TRANSPARENT);
+		return (HBRUSH)m_brushBackground.GetSafeHandle();
+	}
+
+	return hbr;
+}
+
+void CPageProdOverview::OnDestroy()
+{
+	if (m_timerId != 0) {
+		KillTimer(m_timerId);
+		m_timerId = 0;
+	}
+	CDialogEx::OnDestroy();
+}
+
+void CPageProdOverview::OnSize(UINT nType, int cx, int cy)
+{
+	CDialogEx::OnSize(nType, cx, cy);
+}
+
+void CPageProdOverview::OnTimer(UINT_PTR nIDEvent)
+{
+	if (nIDEvent == kTimerRefreshId) {
+		RefreshData();
+	}
+	CDialogEx::OnTimer(nIDEvent);
+}
+
+void CPageProdOverview::RefreshData()
+{
+	auto* pPanel = dynamic_cast<CPanelProduction*>(GetParent());
+	if (pPanel == nullptr) {
+		pPanel = dynamic_cast<CPanelProduction*>(GetParent() ? GetParent()->GetParent() : nullptr);
+	}
+	if (pPanel == nullptr) {
+		m_labelDayOut.setText(_T("--"));
+		m_labelNightOut.setText(_T("--"));
+		m_labelDayTakt.setText(_T("--"));
+		m_labelNightTakt.setText(_T("--"));
+		return;
+	}
+
+	ProductionShiftSummary day;
+	ProductionShiftSummary night;
+	if (!pPanel->TryGetDayNightSummaries(day, night)) {
+		m_labelDayOut.setText(_T("--"));
+		m_labelNightOut.setText(_T("--"));
+		m_labelDayTakt.setText(_T("--"));
+		m_labelNightTakt.setText(_T("--"));
+		return;
+	}
+
+	CString text;
+	text.Format(_T("%lld"), day.output.pairsTotal);
+	m_labelDayOut.setText(text);
+	text.Format(_T("%lld"), night.output.pairsTotal);
+	m_labelNightOut.setText(text);
+
+	text.Format(_T("%.1fs"), day.output.avgTaktSeconds);
+	m_labelDayTakt.setText(text);
+	text.Format(_T("%.1fs"), night.output.avgTaktSeconds);
+	m_labelNightTakt.setText(text);
+}
diff --git a/SourceCode/Bond/Servo/CPageProdOverview.h b/SourceCode/Bond/Servo/CPageProdOverview.h
new file mode 100644
index 0000000..6198d90
--- /dev/null
+++ b/SourceCode/Bond/Servo/CPageProdOverview.h
@@ -0,0 +1,37 @@
+锘�#pragma once
+#include <chrono>
+#include "HmLabel.h"
+
+// CPageProOverview 瀵硅瘽妗�
+class CPageProdOverview : public CDialogEx
+{
+	DECLARE_DYNAMIC(CPageProdOverview)
+
+public:
+	CPageProdOverview(CWnd* pParent = nullptr);   // 鏍囧噯鏋勯�犲嚱鏁�
+	virtual ~CPageProdOverview();
+	void SetBackgroundColor(COLORREF color);
+
+protected:
+	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 鏀寔
+
+	DECLARE_MESSAGE_MAP()
+public:
+	virtual BOOL OnInitDialog();
+	afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
+	afx_msg void OnDestroy();
+	afx_msg void OnSize(UINT nType, int cx, int cy);
+	afx_msg void OnTimer(UINT_PTR nIDEvent);
+
+private:
+	void RefreshData();
+
+private:
+	COLORREF m_clrBackground{ RGB(240, 240, 240) };
+	CBrush m_brushBackground;
+	UINT_PTR m_timerId = 0;
+	CHmLabel m_labelDayOut;
+	CHmLabel m_labelNightOut;
+	CHmLabel m_labelDayTakt;
+	CHmLabel m_labelNightTakt;
+};
diff --git a/SourceCode/Bond/Servo/CPageReport.cpp b/SourceCode/Bond/Servo/CPageReport.cpp
index 0066c9b..4e3fd2e 100644
--- a/SourceCode/Bond/Servo/CPageReport.cpp
+++ b/SourceCode/Bond/Servo/CPageReport.cpp
@@ -5,6 +5,8 @@
 #include "Servo.h"
 #include "CPageReport.h"
 #include "afxdialogex.h"
+#include "CReportEditDlg.h"
+#include <algorithm>
 
 
 // CPageReport 瀵硅瘽妗�
@@ -32,6 +34,7 @@
 	ON_WM_CTLCOLOR()
 	ON_WM_DESTROY()
 	ON_WM_SIZE()
+	ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST1, &CPageReport::OnLvnItemchangedList1)
 END_MESSAGE_MAP()
 
 
@@ -123,3 +126,112 @@
 		m_listCtrl.SetItemText(index, 2, item->getVariablesIdsText().c_str());
 	}
 }
+
+void CPageReport::OnCreateBtns()
+{
+	const int BTN_W = 80;
+	const int BTN_H = 28;
+	CreateBtn(_T("鏂板"), BTN_W, BTN_H, 2001);
+	CreateBtn(_T("鍒犻櫎"), BTN_W, BTN_H, 2002)->EnableWindow(FALSE);
+	CreateBtn(_T("缂栬緫"), BTN_W, BTN_H, 2003)->EnableWindow(FALSE);
+}
+
+void CPageReport::OnLvnItemchangedList1(NMHDR* pNMHDR, LRESULT* pResult)
+{
+	LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
+	int nSelCount = m_listCtrl.GetSelectedCount();
+
+	if (CButton* pDel = GetBtnByName("鍒犻櫎")) {
+		pDel->EnableWindow(nSelCount > 0);
+	}
+	if (CButton* pEdit = GetBtnByName("缂栬緫")) {
+		pEdit->EnableWindow(nSelCount > 0);
+	}
+
+	*pResult = 0;
+}
+
+void CPageReport::OnClickedBtn(const char* btnName)
+{
+	ASSERT(btnName);
+	if (_strcmpi(btnName, "鏂板") == 0) {
+		int rc = UX_CanExecute(L"addReports");
+		if (rc != 1) {
+			AfxMessageBox("鎿嶄綔鏉冮檺涓嶈冻锛岃鑱旂郴绠$悊浜哄憳锛�");
+			return;
+		}
+
+		unsigned int newId = theApp.m_model.m_hsmsPassive.getMaxReportId() + 1;
+		std::vector<unsigned int> initVids;
+		CReportEditDlg dlg(_T("鏂板鎶ュ憡"), static_cast<int>(newId), initVids, this);
+		if (dlg.DoModal() != IDOK) return;
+		const auto& vids = dlg.GetSelectedVids();
+
+		int ret = theApp.m_model.m_hsmsPassive.addReport(static_cast<int>(newId), vids);
+		if (ret == 0) {
+			UX_RecordAction(L"addReports");
+			m_listCtrl.DeleteAllItems();
+			loadReports();
+			if (CButton* pDel = GetBtnByName("鍒犻櫎")) pDel->EnableWindow(FALSE);
+			if (CButton* pEdit = GetBtnByName("缂栬緫")) pEdit->EnableWindow(FALSE);
+		}
+		else {
+			AfxMessageBox(_T("鏂板鎶ュ憡澶辫触锛堝彲鑳絀D閲嶅鎴栨枃浠跺啓鍏ュけ璐ワ級"));
+		}
+	}
+	else if (_strcmpi(btnName, "鍒犻櫎") == 0) {
+		POSITION pos = m_listCtrl.GetFirstSelectedItemPosition();
+		if (pos == nullptr) return;
+		int nItem = m_listCtrl.GetNextSelectedItem(pos);
+		auto pRpt = reinterpret_cast<SERVO::CReport*>(m_listCtrl.GetItemData(nItem));
+		if (pRpt == nullptr) return;
+
+		int rc = UX_CanExecute(L"delReports");
+		if (rc != 1) {
+			AfxMessageBox("鎿嶄綔鏉冮檺涓嶈冻锛岃鑱旂郴绠$悊浜哄憳锛�");
+			return;
+		}
+
+		int ret = theApp.m_model.m_hsmsPassive.deleteReport((int)pRpt->getReportId());
+		if (ret == 0) {
+			UX_RecordAction(L"delReports");
+			m_listCtrl.DeleteAllItems();
+			loadReports();
+			if (CButton* pDel = GetBtnByName("鍒犻櫎")) pDel->EnableWindow(FALSE);
+			if (CButton* pEdit = GetBtnByName("缂栬緫")) pEdit->EnableWindow(FALSE);
+		}
+		else {
+			AfxMessageBox(_T("鍒犻櫎鎶ュ憡澶辫触"));
+		}
+	}
+	else if (_strcmpi(btnName, "缂栬緫") == 0) {
+		POSITION pos = m_listCtrl.GetFirstSelectedItemPosition();
+		if (pos == nullptr) return;
+		int nItem = m_listCtrl.GetNextSelectedItem(pos);
+		auto pRpt = reinterpret_cast<SERVO::CReport*>(m_listCtrl.GetItemData(nItem));
+		if (pRpt == nullptr) return;
+
+		int rc = UX_CanExecute(L"editReports");
+		if (rc != 1) {
+			AfxMessageBox("鎿嶄綔鏉冮檺涓嶈冻锛岃鑱旂郴绠$悊浜哄憳锛�");
+			return;
+		}
+
+		std::vector<unsigned int> vidsExisting = pRpt->getVids();
+		CReportEditDlg dlg(_T("缂栬緫鎶ュ憡"), (int)pRpt->getReportId(), vidsExisting, this);
+		if (dlg.DoModal() != IDOK) return;
+		const auto& vids = dlg.GetSelectedVids();
+
+		int ret = theApp.m_model.m_hsmsPassive.updateReport((int)pRpt->getReportId(), vids);
+		if (ret == 0) {
+			UX_RecordAction(L"editReports");
+			m_listCtrl.DeleteAllItems();
+			loadReports();
+			if (CButton* pDel = GetBtnByName("鍒犻櫎")) pDel->EnableWindow(FALSE);
+			if (CButton* pEdit = GetBtnByName("缂栬緫")) pEdit->EnableWindow(FALSE);
+		}
+		else {
+			AfxMessageBox(_T("缂栬緫鎶ュ憡澶辫触锛堝彲鑳芥枃浠跺啓鍏ュけ璐ワ級"));
+		}
+	}
+}
diff --git a/SourceCode/Bond/Servo/CPageReport.h b/SourceCode/Bond/Servo/CPageReport.h
index 716ac0b..bec5588 100644
--- a/SourceCode/Bond/Servo/CPageReport.h
+++ b/SourceCode/Bond/Servo/CPageReport.h
@@ -16,6 +16,8 @@
 
 private:
 	CListCtrlEx m_listCtrl;
+	void OnCreateBtns() override;
+	void OnClickedBtn(const char* btnName) override;
 
 // 瀵硅瘽妗嗘暟鎹�
 #ifdef AFX_DESIGN_TIME
@@ -31,4 +33,5 @@
 	afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
 	afx_msg void OnDestroy();
 	afx_msg void OnSize(UINT nType, int cx, int cy);
+	afx_msg void OnLvnItemchangedList1(NMHDR* pNMHDR, LRESULT* pResult);
 };
diff --git a/SourceCode/Bond/Servo/CPageVarialbles.cpp b/SourceCode/Bond/Servo/CPageVarialbles.cpp
index 5f4f5cc..f2c2587 100644
--- a/SourceCode/Bond/Servo/CPageVarialbles.cpp
+++ b/SourceCode/Bond/Servo/CPageVarialbles.cpp
@@ -5,6 +5,7 @@
 #include "Servo.h"
 #include "CPageVarialbles.h"
 #include "afxdialogex.h"
+#include "CVariableEditDlg2.h"
 
 
 // CPageVarialbles 瀵硅瘽妗�
@@ -32,6 +33,7 @@
 	ON_WM_CTLCOLOR()
 	ON_WM_DESTROY()
 	ON_WM_SIZE()
+	ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST1, &CPageVarialbles::OnLvnItemchangedList1)
 END_MESSAGE_MAP()
 
 
@@ -130,3 +132,112 @@
 		m_listCtrl.SetItemText(index, 4, item->getRemark().c_str());
 	}
 }
+
+void CPageVarialbles::OnCreateBtns()
+{
+	const int BTN_W = 80;
+	const int BTN_H = 28;
+	CreateBtn(_T("鏂板"), BTN_W, BTN_H, 1001);
+	CreateBtn(_T("鍒犻櫎"), BTN_W, BTN_H, 1002)->EnableWindow(FALSE);
+	CreateBtn(_T("缂栬緫"), BTN_W, BTN_H, 1003)->EnableWindow(FALSE);
+}
+
+void CPageVarialbles::OnLvnItemchangedList1(NMHDR* pNMHDR, LRESULT* pResult)
+{
+	LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
+	int nSelCount = m_listCtrl.GetSelectedCount();
+
+	// 鏍规嵁閫変腑鐘舵�佸惎鐢�/绂佺敤鎸夐挳
+	if (CButton* pDel = GetBtnByName("鍒犻櫎")) {
+		pDel->EnableWindow(nSelCount > 0);
+	}
+	if (CButton* pEdit = GetBtnByName("缂栬緫")) {
+		pEdit->EnableWindow(nSelCount > 0);
+	}
+
+	*pResult = 0;
+}
+
+void CPageVarialbles::OnClickedBtn(const char* btnName)
+{
+	ASSERT(btnName);
+	if (_strcmpi(btnName, "鏂板") == 0) {
+		int rc = UX_CanExecute(L"addVarialbles");
+		if (rc != 1) {
+			AfxMessageBox("鎿嶄綔鏉冮檺涓嶈冻锛岃鑱旂郴绠$悊浜哄憳锛�");
+			return;
+		}
+		unsigned int newId = theApp.m_model.m_hsmsPassive.getMaxVariableId();
+		int newIdInt = static_cast<int>(newId + 1);
+		CVariableEditDlg2 dlg(_T("鏂板鍙橀噺"), newIdInt, _T("U1"), _T(""), _T(""), this);
+		if (dlg.DoModal() != IDOK) return;
+		CString name = dlg.GetNameText();
+		CString fmt = dlg.GetTypeText();
+		CString remark = dlg.GetRemark();
+
+		int ret = theApp.m_model.m_hsmsPassive.addVariable(CT2A(name), CT2A(fmt), CT2A(remark), newIdInt);
+		if (ret == 0) {
+			UX_RecordAction(L"addVarialbles");
+			m_listCtrl.DeleteAllItems();
+			loadVariables();
+		}
+		else {
+			AfxMessageBox(_T("鏂板鍙橀噺澶辫触锛屾牸寮忔槸鍚︽纭紵(U1/U2/I2/A20/A50/L)"));
+		}
+	}
+	else if (_strcmpi(btnName, "鍒犻櫎") == 0) {
+		POSITION pos = m_listCtrl.GetFirstSelectedItemPosition();
+		if (pos == nullptr) return;
+		int nItem = m_listCtrl.GetNextSelectedItem(pos);
+		auto pVar = reinterpret_cast<SERVO::CVariable*>(m_listCtrl.GetItemData(nItem));
+		if (pVar == nullptr) return;
+
+		int rc = UX_CanExecute(L"delVarialbles");
+		if (rc != 1) {
+			AfxMessageBox("鎿嶄綔鏉冮檺涓嶈冻锛岃鑱旂郴绠$悊浜哄憳锛�");
+			return;
+		}
+		int ret = theApp.m_model.m_hsmsPassive.deleteVariable(pVar->getVarialbleId());
+		if (ret == 0) {
+			UX_RecordAction(L"delVarialbles");
+
+			m_listCtrl.DeleteAllItems();
+			loadVariables();
+			if (CButton* pDel = GetBtnByName("鍒犻櫎")) pDel->EnableWindow(FALSE);
+			if (CButton* pEdit = GetBtnByName("缂栬緫")) pEdit->EnableWindow(FALSE);
+		}
+	}
+	else if (_strcmpi(btnName, "缂栬緫") == 0) {
+		POSITION pos = m_listCtrl.GetFirstSelectedItemPosition();
+		if (pos == nullptr) return;
+		int nItem = m_listCtrl.GetNextSelectedItem(pos);
+		auto pVar = reinterpret_cast<SERVO::CVariable*>(m_listCtrl.GetItemData(nItem));
+		if (pVar == nullptr) return;
+
+		int rc = UX_CanExecute(L"editVarialbles");
+		if (rc != 1) {
+			AfxMessageBox("鎿嶄綔鏉冮檺涓嶈冻锛岃鑱旂郴绠$悊浜哄憳锛�");
+			return;
+		}
+		CVariableEditDlg2 dlg(_T("缂栬緫鍙橀噺"),
+			pVar->getVarialbleId(),
+			CString(CA2T(SERVO::CVariable::formatToString(pVar->getFormat()).c_str())),
+			CString(CA2T(pVar->getName().c_str())),
+			CString(CA2T(pVar->getRemark().c_str())),
+			this);
+		if (dlg.DoModal() != IDOK) return;
+		CString name = dlg.GetNameText();
+		CString fmt = dlg.GetTypeText();
+		CString remark = dlg.GetRemark();
+
+		int ret = theApp.m_model.m_hsmsPassive.updateVariable(pVar->getVarialbleId(), CT2A(name), CT2A(fmt), CT2A(remark));
+		if (ret == 0) {
+			UX_RecordAction(L"editVarialbles");
+			m_listCtrl.DeleteAllItems();
+			loadVariables();
+		}
+		else {
+			AfxMessageBox(_T("缂栬緫鍙橀噺澶辫触锛屾牸寮忔槸鍚︽纭紵(U1/U2/I2/A20/A50/L)"));
+		}
+	}
+}
diff --git a/SourceCode/Bond/Servo/CPageVarialbles.h b/SourceCode/Bond/Servo/CPageVarialbles.h
index 3ccbdc0..3898e68 100644
--- a/SourceCode/Bond/Servo/CPageVarialbles.h
+++ b/SourceCode/Bond/Servo/CPageVarialbles.h
@@ -14,9 +14,13 @@
 	virtual ~CPageVarialbles();
 	virtual void OnApply();
 	void loadVariables();
+	virtual void OnCreateBtns();
 
 private:
 	CListCtrlEx m_listCtrl;
+
+protected:
+	virtual void OnClickedBtn(const char* btnName) override;
 
 
 // 瀵硅瘽妗嗘暟鎹�
@@ -33,4 +37,5 @@
 	afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
 	afx_msg void OnDestroy();
 	afx_msg void OnSize(UINT nType, int cx, int cy);
+	afx_msg void OnLvnItemchangedList1(NMHDR* pNMHDR, LRESULT* pResult);
 };
diff --git a/SourceCode/Bond/Servo/CPanelMaster.cpp b/SourceCode/Bond/Servo/CPanelMaster.cpp
index 82c5361..4fd3dbb 100644
--- a/SourceCode/Bond/Servo/CPanelMaster.cpp
+++ b/SourceCode/Bond/Servo/CPanelMaster.cpp
@@ -50,6 +50,11 @@
 	return m_nPanelWidth;
 }
 
+void CPanelMaster::setPanelWidth(int width)
+{
+	m_nPanelWidth = width;
+}
+
 BOOL CPanelMaster::OnInitDialog()
 {
 	CDialogEx::OnInitDialog();
@@ -59,13 +64,6 @@
 	pLine1->SetBkgndColor(RGB(225, 225, 225));
 	pLine1->SetLineColor(RGB(198, 198, 198));
 	pLine1->EnableResize();
-
-
-	// 璇诲彇闈㈡澘瀹�
-	CString strIniFile;
-	strIniFile.Format(_T("%s\\%s.ini"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir, (LPTSTR)(LPCTSTR)theApp.m_strAppFile);
-	m_nPanelWidth = GetPrivateProfileInt(_T("App"), _T("MasterPanelWidth"),
-		int((double)GetSystemMetrics(SM_CXSCREEN) * 0.25), (LPTSTR)(LPCTSTR)strIniFile);
 
 
 	// treectrl
@@ -132,12 +130,6 @@
 	m_nPanelWidth = max(m_nPanelWidth, MASTER_PANEL_MIN_WIDTH);
 	m_nPanelWidth = min(m_nPanelWidth, MASTER_PANEL_MAX_WIDTH);
 	GetParent()->SendMessage(ID_MSG_PANEL_RESIZE, m_nPanelWidth, 0);
-
-	CString strIniFile, strValue;
-	strIniFile.Format(_T("%s\\%s.ini"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir, (LPTSTR)(LPCTSTR)theApp.m_strAppFile);
-	strValue.Format(_T("%d"), m_nPanelWidth);
-	WritePrivateProfileString(_T("App"), _T("MasterPanelWidth"),
-		(LPTSTR)(LPCTSTR)strValue, (LPTSTR)(LPCTSTR)strIniFile);
 	OnSize(0, 0, 0);
 	
 	* result = 0;
diff --git a/SourceCode/Bond/Servo/CPanelMaster.h b/SourceCode/Bond/Servo/CPanelMaster.h
index 15bd2db..481683a 100644
--- a/SourceCode/Bond/Servo/CPanelMaster.h
+++ b/SourceCode/Bond/Servo/CPanelMaster.h
@@ -12,6 +12,7 @@
 	CPanelMaster(CWnd* pParent = nullptr);   // 鏍囧噯鏋勯�犲嚱鏁�
 	virtual ~CPanelMaster();
 	int getPanelWidth();
+	void setPanelWidth(int width);
 	void loadEquipmentList();
 	void loadSteps(SERVO::CEquipment* pEquipment, HTREEITEM hItemEq);
 	SERVO::CEquipment* GetActiveEquipment();
diff --git a/SourceCode/Bond/Servo/CPanelProduction.cpp b/SourceCode/Bond/Servo/CPanelProduction.cpp
new file mode 100644
index 0000000..f3a5ab2
--- /dev/null
+++ b/SourceCode/Bond/Servo/CPanelProduction.cpp
@@ -0,0 +1,236 @@
+锘�// CPanelProduction.cpp
+//
+
+#include "stdafx.h"
+#include "Servo.h"
+#include "CPanelProduction.h"
+#include "afxdialogex.h"
+#include "Common.h"
+#include "VerticalLine.h"
+
+
+// CPanelProduction dialog
+
+IMPLEMENT_DYNAMIC(CPanelProduction, CDialogEx)
+
+CPanelProduction::CPanelProduction(CWnd* pParent /*=nullptr*/)
+	: CDialogEx(IDD_PANEL_PRODUCTION, pParent)
+{
+	m_crBkgnd = PANEL_PRODUCTION_BACKGROUND_COLOR;
+	m_hbrBkgnd = nullptr;
+	m_nPanelWidth = 288;
+	m_hPlaceholder = nullptr;
+	m_bShiftSummaryValid = FALSE;
+	m_pStatsThread = nullptr;
+	m_pAccordionWnd = nullptr;
+	m_pPageProdOverview = nullptr;
+	m_pPageCtrlState = nullptr;
+}
+
+CPanelProduction::~CPanelProduction()
+{
+}
+
+void CPanelProduction::DoDataExchange(CDataExchange* pDX)
+{
+	CDialogEx::DoDataExchange(pDX);
+}
+
+
+BEGIN_MESSAGE_MAP(CPanelProduction, CDialogEx)
+	ON_WM_CTLCOLOR()
+	ON_WM_DESTROY()
+	ON_WM_SIZE()
+	ON_NOTIFY(BYVERTICALLINE_MOVEX, IDC_LINE1, &CPanelProduction::OnVLineMoveX)
+	ON_BN_CLICKED(IDC_BUTTON_CLOSE, &CPanelProduction::OnBnClickedButtonClose)
+	ON_WM_TIMER()
+END_MESSAGE_MAP()
+
+int CPanelProduction::getPanelWidth()
+{
+	return m_nPanelWidth;
+}
+
+void CPanelProduction::setPanelWidth(int width)
+{
+	m_nPanelWidth = width;
+}
+
+BOOL CPanelProduction::OnInitDialog()
+{
+	CDialogEx::OnInitDialog();
+
+	CVerticalLine* pLine1 = CVerticalLine::Hook(GetDlgItem(IDC_LINE1)->GetSafeHwnd());
+	pLine1->SetBkgndColor(RGB(225, 225, 225));
+	pLine1->SetLineColor(RGB(198, 198, 198));
+	pLine1->EnableResize();
+
+	CString strExpandIcon, strCloseIcon;
+	strExpandIcon.Format(_T("%s\\res\\arrow_down.ico"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir);
+	strCloseIcon.Format(_T("%s\\res\\arrow_right.ico"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir);
+
+	m_pAccordionWnd = CAccordionWnd::FromHandle(GetDlgItem(IDC_ACCORDION_WND1)->m_hWnd);
+	m_pAccordionWnd->SetBkgndColor(m_crBkgnd);
+	m_pAccordionWnd->SetFrameColor(RGB(220, 220, 200), TRUE);
+	m_pAccordionWnd->Setpadding(PADDING_LEFT, 2);
+	m_pAccordionWnd->Setpadding(PADDING_TOP, 2);
+	m_pAccordionWnd->Setpadding(PADDING_RIGHT, 2);
+	m_pAccordionWnd->Setpadding(PADDING_BOTTOM, 2);
+	m_pAccordionWnd->LoadExpandIcon(strExpandIcon, strCloseIcon);
+
+	m_pPageCtrlState = new CPageCtrlState();
+	m_pPageCtrlState->SetBackgroundColor(m_crBkgnd);
+	m_pPageCtrlState->Create(IDD_PROD_CTRL_STATE, GetDlgItem(IDC_ACCORDION_WND1));
+	m_pPageCtrlState->ShowWindow(SW_HIDE);
+	m_pAccordionWnd->AddItem("鐘舵��", m_pPageCtrlState, 120, TRUE, TRUE);
+
+	m_pPageProdOverview = new CPageProdOverview();
+	m_pPageProdOverview->SetBackgroundColor(m_crBkgnd);
+	m_pPageProdOverview->Create(IDD_PROD_OVERVIEW, GetDlgItem(IDC_ACCORDION_WND1));
+	m_pPageProdOverview->ShowWindow(SW_HIDE);
+	m_pAccordionWnd->AddItem("鐢熶骇鎬昏", m_pPageProdOverview, 280, TRUE, TRUE);
+
+	SetTimer(1, 1000 * 10, nullptr);
+	StartStatsThread();
+
+	return TRUE;  // return TRUE unless you set the focus to a control
+				  // Exception: OCX property pages should return FALSE
+}
+
+HBRUSH CPanelProduction::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
+{
+	HBRUSH hbr = CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);
+
+	if (nCtlColor == CTLCOLOR_STATIC) {
+		pDC->SetBkColor(m_crBkgnd);
+		pDC->SetTextColor(RGB(0, 0, 0));
+	}
+
+	if (m_hbrBkgnd == nullptr) {
+		m_hbrBkgnd = CreateSolidBrush(m_crBkgnd);
+	}
+
+	return m_hbrBkgnd;
+}
+
+void CPanelProduction::OnDestroy()
+{
+	StopStatsThread();
+
+	CDialogEx::OnDestroy();
+
+	if (m_pPageCtrlState != nullptr) {
+		m_pPageCtrlState->DestroyWindow();
+		delete m_pPageCtrlState;
+		m_pPageCtrlState = nullptr;
+	}
+	if (m_pPageProdOverview != nullptr) {
+		m_pPageProdOverview->DestroyWindow();
+		delete m_pPageProdOverview;
+		m_pPageProdOverview = nullptr;
+	}
+
+	if (m_hbrBkgnd != nullptr) {
+		::DeleteObject(m_hbrBkgnd);
+	}
+}
+
+void CPanelProduction::OnSize(UINT nType, int cx, int cy)
+{
+	CDialogEx::OnSize(nType, cx, cy);
+	if (GetDlgItem(IDC_LINE1) == nullptr) return;
+
+	CWnd* pItem;
+	CRect rcClient, rcItem;
+
+	GetClientRect(&rcClient);
+	pItem = GetDlgItem(IDC_LINE1);
+	pItem->MoveWindow(rcClient.right - 3, 0, 3, rcClient.Height());
+
+	pItem = GetDlgItem(IDC_ACCORDION_WND1);
+	pItem->MoveWindow(5, 5, rcClient.Width() - 10, rcClient.Height() - 10);
+}
+
+#define PRODUCTION_PANEL_MIN_WIDTH		88
+#define PRODUCTION_PANEL_MAX_WIDTH		588
+void CPanelProduction::OnVLineMoveX(NMHDR* nmhdr, LRESULT* result)
+{
+	BYVERTICALLINE_NMHDR* pNmhdrex = (BYVERTICALLINE_NMHDR*)nmhdr;
+	int x = pNmhdrex->dwData;
+	m_nPanelWidth += x;
+	m_nPanelWidth = max(m_nPanelWidth, PRODUCTION_PANEL_MIN_WIDTH);
+	m_nPanelWidth = min(m_nPanelWidth, PRODUCTION_PANEL_MAX_WIDTH);
+	GetParent()->SendMessage(ID_MSG_PANEL_RESIZE, m_nPanelWidth, 0);
+	OnSize(0, 0, 0);
+
+	*result = 0;
+}
+
+void CPanelProduction::OnBnClickedButtonClose()
+{
+	CWnd* pParent = GetParent();
+	if (pParent != nullptr) {
+		pParent->PostMessage(WM_COMMAND, ID_MENU_WND_TEST_PANEL, 0);
+	}
+}
+
+BOOL CPanelProduction::TryGetDayNightSummaries(ProductionShiftSummary& outDay, ProductionShiftSummary& outNight)
+{
+	CSingleLock lock(&m_csShiftSummary, TRUE);
+	if (!m_bShiftSummaryValid) return FALSE;
+	outDay = m_daySummary;
+	outNight = m_nightSummary;
+	return TRUE;
+}
+
+void CPanelProduction::StartStatsThread()
+{
+	if (m_pStatsThread != nullptr) return;
+
+	m_evStopStats.ResetEvent();
+
+	m_pStatsThread = AfxBeginThread(&CPanelProduction::StatsThreadProc, this, THREAD_PRIORITY_BELOW_NORMAL, 0, 0);
+	if (m_pStatsThread != nullptr) {
+		m_pStatsThread->m_bAutoDelete = FALSE;
+	}
+}
+
+void CPanelProduction::StopStatsThread()
+{
+	if (m_pStatsThread == nullptr) return;
+
+	m_evStopStats.SetEvent();
+	const DWORD rc = WaitForSingleObject(m_pStatsThread->m_hThread, 5000);
+	if (rc == WAIT_OBJECT_0) {
+		delete m_pStatsThread;
+	}
+	m_pStatsThread = nullptr;
+}
+
+UINT CPanelProduction::StatsThreadProc(LPVOID pParam)
+{
+	CPanelProduction* self = reinterpret_cast<CPanelProduction*>(pParam);
+	if (self == nullptr) return 0;
+
+	const DWORD intervalMs = 5000;
+	for (;;) {
+		if (self->m_evStopStats.Lock(intervalMs)) break;
+
+		ProductionShiftSummary daySummary;
+		ProductionShiftSummary nightSummary;
+		if (ProductionStats::ComputeDayNightSummaries(theApp.m_model.m_configuration, daySummary, nightSummary)) {
+			CSingleLock lock(&self->m_csShiftSummary, TRUE);
+			self->m_daySummary = std::move(daySummary);
+			self->m_nightSummary = std::move(nightSummary);
+			self->m_bShiftSummaryValid = TRUE;
+		}
+	}
+
+	return 0;
+}
+
+void CPanelProduction::OnTimer(UINT_PTR nIDEvent)
+{
+	// TODO: 鍦ㄦ娣诲姞娑堟伅澶勭悊绋嬪簭浠g爜鍜�/鎴栬皟鐢ㄩ粯璁ゅ��
+	CDialogEx::OnTimer(nIDEvent);
+}
diff --git a/SourceCode/Bond/Servo/CPanelProduction.h b/SourceCode/Bond/Servo/CPanelProduction.h
new file mode 100644
index 0000000..a4bd705
--- /dev/null
+++ b/SourceCode/Bond/Servo/CPanelProduction.h
@@ -0,0 +1,64 @@
+锘�#pragma once
+#include "BlButton.h"
+#include <afxmt.h>
+#include "AccordionWnd.h"
+#include "ProductionStats.h"
+#include "CPageProdOverview.h"
+#include "CPageCtrlState.h"
+
+// CPanelProduction dialog
+class CPanelProduction : public CDialogEx
+{
+	DECLARE_DYNAMIC(CPanelProduction)
+
+public:
+	CPanelProduction(CWnd* pParent = nullptr);   // standard constructor
+	virtual ~CPanelProduction();
+	int getPanelWidth();
+	void setPanelWidth(int width);
+
+private:
+	COLORREF m_crBkgnd;
+	HBRUSH m_hbrBkgnd;
+	int m_nPanelWidth;
+	CBlButton m_btnClose;
+	HWND m_hPlaceholder;
+	CAccordionWnd* m_pAccordionWnd;
+
+	// Production shift summaries (updated by background thread)
+	ProductionShiftSummary m_daySummary;
+	ProductionShiftSummary m_nightSummary;
+	BOOL m_bShiftSummaryValid;
+	CCriticalSection m_csShiftSummary;
+	CWinThread* m_pStatsThread;
+	CEvent m_evStopStats;
+	CPageProdOverview* m_pPageProdOverview;
+	CPageCtrlState* m_pPageCtrlState;
+
+protected:
+	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
+
+// 瀵硅瘽妗嗘暟鎹�
+#ifdef AFX_DESIGN_TIME
+	enum { IDD = IDD_PANEL_PRODUCTION };
+#endif
+
+	DECLARE_MESSAGE_MAP()
+public:
+	virtual BOOL OnInitDialog();
+	afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
+	afx_msg void OnDestroy();
+	afx_msg void OnSize(UINT nType, int cx, int cy);
+	afx_msg void OnVLineMoveX(NMHDR* nmhdr, LRESULT* result);
+	afx_msg void OnBnClickedButtonClose();
+
+	// Thread-safe snapshots for UI timer display
+	BOOL TryGetDayNightSummaries(ProductionShiftSummary& outDay, ProductionShiftSummary& outNight);
+
+private:
+	static UINT AFX_CDECL StatsThreadProc(LPVOID pParam);
+	void StartStatsThread();
+	void StopStatsThread();
+public:
+	afx_msg void OnTimer(UINT_PTR nIDEvent);
+};
diff --git a/SourceCode/Bond/Servo/CParam.cpp b/SourceCode/Bond/Servo/CParam.cpp
index 57a8f0c..9162b12 100644
--- a/SourceCode/Bond/Servo/CParam.cpp
+++ b/SourceCode/Bond/Servo/CParam.cpp
@@ -53,7 +53,7 @@
 	return m_nValueType;
 }
 
-int CParam::getIntValue()
+int CParam::getIntValue() const
 {
 	return m_nValue;
 }
@@ -63,7 +63,7 @@
 	m_nValue = value;
 }
 
-double CParam::getDoubleValue()
+double CParam::getDoubleValue() const
 {
 	if(m_nValueType == PVT_DOUBLE)
 		return m_fValue;
diff --git a/SourceCode/Bond/Servo/CParam.h b/SourceCode/Bond/Servo/CParam.h
index f8562a1..3e8dd1c 100644
--- a/SourceCode/Bond/Servo/CParam.h
+++ b/SourceCode/Bond/Servo/CParam.h
@@ -17,9 +17,9 @@
 	std::string& getName();
 	std::string& getUnit();
 	int getValueType();
-	int getIntValue();
+	int getIntValue() const;
 	void setIntValue(int value);
-	double getDoubleValue();
+	double getDoubleValue() const;
 	void setDoubleValue(double value);
 	void Serialize(CArchive& ar);
 
diff --git a/SourceCode/Bond/Servo/CReadStep.cpp b/SourceCode/Bond/Servo/CReadStep.cpp
index c6aa36f..06181b8 100644
--- a/SourceCode/Bond/Servo/CReadStep.cpp
+++ b/SourceCode/Bond/Servo/CReadStep.cpp
@@ -95,6 +95,7 @@
 					ASSERT(m_pEquipment);
 					m_pEquipment->onStepEvent(this, STEP_EVENT_READDATA);
 				}
+				if (m_bReadContinue) continue;
 
 				// 0426新增
 				// 1.1,写return code or data
diff --git a/SourceCode/Bond/Servo/CReadStep.h b/SourceCode/Bond/Servo/CReadStep.h
index dcb3c56..8d048a4 100644
--- a/SourceCode/Bond/Servo/CReadStep.h
+++ b/SourceCode/Bond/Servo/CReadStep.h
@@ -11,6 +11,7 @@
 
 	public:
 		unsigned WorkingProc();
+		virtual void setReadContinue(BOOL bContinue) { m_bReadContinue = bContinue; };
 		virtual void setWriteSignalDev(int dev);
 		virtual void setReturnDev(int dev);
 		virtual void onReadSignal(int nSignalType);
@@ -40,6 +41,7 @@
 		char m_szReturnBuf[1024];
 		int m_nReturnDataSize;
 		int m_nReturnDevNo;
+		BOOL m_bReadContinue{ FALSE };
 	};
 }
 
diff --git a/SourceCode/Bond/Servo/CReport.cpp b/SourceCode/Bond/Servo/CReport.cpp
index 1ab15fa..29d57d3 100644
--- a/SourceCode/Bond/Servo/CReport.cpp
+++ b/SourceCode/Bond/Servo/CReport.cpp
@@ -8,12 +8,10 @@
 		m_nReportId = 0;
 	}
 
-	CReport::CReport(unsigned int reportId, std::vector<unsigned int>& vids)
+	CReport::CReport(unsigned int reportId, const std::vector<unsigned int>& vids)
 	{
 		m_nReportId = reportId;
-		for (auto vid : vids) {
-			m_vids.push_back(vid);
-		}
+		m_vids = vids;
 	}
 
 	CReport::~CReport()
diff --git a/SourceCode/Bond/Servo/CReport.h b/SourceCode/Bond/Servo/CReport.h
index 191d539..41952df 100644
--- a/SourceCode/Bond/Servo/CReport.h
+++ b/SourceCode/Bond/Servo/CReport.h
@@ -7,7 +7,7 @@
 	{
 	public:
 		CReport();
-		CReport(unsigned int reportId, std::vector<unsigned int>& vids);
+		CReport(unsigned int reportId, const std::vector<unsigned int>& vids);
 		virtual ~CReport();
 
 	public:
@@ -18,6 +18,7 @@
 		std::vector<CVariable*>& getVariables();
 		std::string getVariablesIdsText();
 		bool getVariableName(unsigned int vid, std::string& strName);
+		const std::vector<unsigned int>& getVids() const { return m_vids; }
 
 	private:
 		unsigned int m_nReportId;
diff --git a/SourceCode/Bond/Servo/CReportEditDlg.cpp b/SourceCode/Bond/Servo/CReportEditDlg.cpp
new file mode 100644
index 0000000..5dd368b
--- /dev/null
+++ b/SourceCode/Bond/Servo/CReportEditDlg.cpp
@@ -0,0 +1,78 @@
+锘�#include "stdafx.h"
+#include "CReportEditDlg.h"
+#include "Servo.h"
+#include "resource.h"
+#include <algorithm>
+
+IMPLEMENT_DYNAMIC(CReportEditDlg, CDialogEx)
+
+CReportEditDlg::CReportEditDlg(const CString& title, int rptId, const std::vector<unsigned int>& vids, CWnd* pParent)
+	: CDialogEx(IDD_DIALOG_REPORT_EDIT, pParent)
+	, m_strTitle(title)
+	, m_rptId(rptId)
+	, m_vids(vids)
+{
+}
+
+CReportEditDlg::~CReportEditDlg()
+{
+}
+
+void CReportEditDlg::DoDataExchange(CDataExchange* pDX)
+{
+	CDialogEx::DoDataExchange(pDX);
+	DDX_Control(pDX, IDC_EDIT_RPT_ID, m_editId);
+	DDX_Control(pDX, IDC_LIST_RPT_VARS, m_listVars);
+}
+
+BEGIN_MESSAGE_MAP(CReportEditDlg, CDialogEx)
+END_MESSAGE_MAP()
+
+BOOL CReportEditDlg::OnInitDialog()
+{
+	CDialogEx::OnInitDialog();
+	SetWindowText(m_strTitle);
+
+	CString strId;
+	strId.Format(_T("%d"), m_rptId);
+	m_editId.SetWindowText(strId);
+	m_editId.SetReadOnly(TRUE);
+
+	// 鍒濆鍖栧垪琛�
+	m_listVars.SetExtendedStyle(m_listVars.GetExtendedStyle() | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_CHECKBOXES);
+	m_listVars.InsertColumn(0, _T("ID"), LVCFMT_LEFT, 60);
+	m_listVars.InsertColumn(1, _T("鍚嶇О"), LVCFMT_LEFT, 140);
+	m_listVars.InsertColumn(2, _T("鏍煎紡"), LVCFMT_LEFT, 80);
+
+	auto& vars = theApp.m_model.m_hsmsPassive.getVariables();
+	for (int i = 0; i < (int)vars.size(); ++i) {
+		auto v = vars[i];
+		if (v == nullptr) continue;
+		int idx = m_listVars.InsertItem(m_listVars.GetItemCount(), std::to_string(v->getVarialbleId()).c_str());
+		m_listVars.SetItemText(idx, 1, v->getName().c_str());
+		m_listVars.SetItemText(idx, 2, SERVO::CVariable::formatToString(v->getFormat()).c_str());
+		m_listVars.SetItemData(idx, (DWORD_PTR)v->getVarialbleId());
+		if (std::find(m_vids.begin(), m_vids.end(), v->getVarialbleId()) != m_vids.end()) {
+			m_listVars.SetCheck(idx, TRUE);
+		}
+	}
+
+	return TRUE;
+}
+
+void CReportEditDlg::OnOK()
+{
+	std::vector<unsigned int> selected;
+	int count = m_listVars.GetItemCount();
+	for (int i = 0; i < count; ++i) {
+		if (m_listVars.GetCheck(i)) {
+			selected.push_back((unsigned int)m_listVars.GetItemData(i));
+		}
+	}
+	if (selected.empty()) {
+		AfxMessageBox(_T("鑷冲皯閫夋嫨涓�涓彉閲�"));
+		return;
+	}
+	m_vids.swap(selected);
+	CDialogEx::OnOK();
+}
diff --git a/SourceCode/Bond/Servo/CReportEditDlg.h b/SourceCode/Bond/Servo/CReportEditDlg.h
new file mode 100644
index 0000000..00fc4dc
--- /dev/null
+++ b/SourceCode/Bond/Servo/CReportEditDlg.h
@@ -0,0 +1,30 @@
+锘�#pragma once
+#include "afxdialogex.h"
+
+// 鎶ュ憡缂栬緫瀵硅瘽妗嗭紙鏂板/缂栬緫鍏辩敤锛�
+class CReportEditDlg : public CDialogEx
+{
+	DECLARE_DYNAMIC(CReportEditDlg)
+
+public:
+	CReportEditDlg(const CString& title, int rptId, const std::vector<unsigned int>& vids, CWnd* pParent = nullptr);
+	virtual ~CReportEditDlg();
+
+	int GetReportId() const { return m_rptId; }
+	const std::vector<unsigned int>& GetSelectedVids() const { return m_vids; }
+
+protected:
+	virtual BOOL OnInitDialog() override;
+	virtual void DoDataExchange(CDataExchange* pDX) override;
+	afx_msg void OnOK();
+
+	DECLARE_MESSAGE_MAP()
+
+private:
+	CString m_strTitle;
+	int m_rptId;
+	std::vector<unsigned int> m_vids;
+
+	CEdit m_editId;
+	CListCtrl m_listVars;
+};
diff --git a/SourceCode/Bond/Servo/CSVData.cpp b/SourceCode/Bond/Servo/CSVData.cpp
index fbda14e..a60a7f6 100644
--- a/SourceCode/Bond/Servo/CSVData.cpp
+++ b/SourceCode/Bond/Servo/CSVData.cpp
@@ -26,31 +26,35 @@
 
 	int CSVData::serialize(char* pszBuffer, int nBufferSize)
 	{
-		if (nBufferSize < 133) return -1;
+		if (nBufferSize < 133 * 2) return -1;
 
 		int index = 0;
-		CToolUnits::convertString(&pszBuffer[index], 8, m_strTime);
-		index += 8;
+		CToolUnits::convertString(&pszBuffer[index], 8 * 2, m_strTime);
+		index += 8 * 2;
 
-		memcpy(&pszBuffer[index], m_svRawData.data(), 125);
-		index += 125;
+		memcpy(&pszBuffer[index], m_svRawData.data(), 125 * 2);
+		index += 125 * 2;
 
-		return 133;
+		return 133 * 2;
 	}
 
 	int CSVData::unserialize(const char* pszBuffer, int nBufferSize)
 	{
-		if (nBufferSize < 133) return -1;
+		if (pszBuffer == nullptr) return -1;
+		if (nBufferSize < 133 * 2) return -1;
 
 		int index = 0;
-		CSVData svData;
 		CToolUnits::convertString(&pszBuffer[index], 8 * 2, m_strTime);
 		index += 8 * 2;
 
 		m_svRawData.clear();
-		m_svRawData.insert(m_svRawData.end(), (uint8_t*)(&pszBuffer[index]), (uint8_t*)(pszBuffer)+(125 * 2));
+		if (nBufferSize < index + 125 * 2) return -1;
+		m_svRawData.insert(
+			m_svRawData.end(),
+			(const uint8_t*)&pszBuffer[index],
+			(const uint8_t*)&pszBuffer[index + 125 * 2]);
 		index += 125 * 2;
 
-		return 133;
+		return 133 * 2;
 	}
 }
diff --git a/SourceCode/Bond/Servo/CServoUtilsTool.cpp b/SourceCode/Bond/Servo/CServoUtilsTool.cpp
index c9d06f6..94bb8f7 100644
--- a/SourceCode/Bond/Servo/CServoUtilsTool.cpp
+++ b/SourceCode/Bond/Servo/CServoUtilsTool.cpp
@@ -1,32 +1,37 @@
-#include "stdafx.h"
+锘�#include "stdafx.h"
 #include "CServoUtilsTool.h"
 #include "Common.h"
 
 
 namespace SERVO {
 	static std::unordered_map<int, std::vector<std::string>> EQ_DATA_TYPES = {
-		{EQ_ID_Bonder1, {
-			"气囊压力", "上腔压力", "管道真空规值", "腔体真空规值",
-			"上腔温度1", "上腔温度2", "上腔温度3", "上腔温度4",
-			"上腔温度5", "上腔温度6", "下腔温度1", "下腔温度2",
-			"下腔温度3", "下腔温度4", "下腔温度5", "下腔温度6"
+		{MID_Bonder1, {
+			"姘斿泭鍘嬪姏", "涓婅厰鍘嬪姏", "绠¢亾鐪熺┖瑙勫��", "鑵斾綋鐪熺┖瑙勫��",
+			"涓婅厰娓╁害1", "涓婅厰娓╁害2", "涓婅厰娓╁害3", "涓婅厰娓╁害4",
+			"涓婅厰娓╁害5", "涓婅厰娓╁害6", "涓嬭厰娓╁害1", "涓嬭厰娓╁害2",
+			"涓嬭厰娓╁害3", "涓嬭厰娓╁害4", "涓嬭厰娓╁害5", "涓嬭厰娓╁害6"
 		}},
-		{EQ_ID_Bonder2, {
-			"气囊压力", "上腔压力", "管道真空规值", "腔体真空规值",
-			"上腔温度1", "上腔温度2", "上腔温度3", "上腔温度4",
-			"上腔温度5", "上腔温度6", "下腔温度1", "下腔温度2",
-			"下腔温度3", "下腔温度4", "下腔温度5", "下腔温度6"
+		{MID_Bonder2, {
+			"姘斿泭鍘嬪姏", "涓婅厰鍘嬪姏", "绠¢亾鐪熺┖瑙勫��", "鑵斾綋鐪熺┖瑙勫��",
+			"涓婅厰娓╁害1", "涓婅厰娓╁害2", "涓婅厰娓╁害3", "涓婅厰娓╁害4",
+			"涓婅厰娓╁害5", "涓婅厰娓╁害6", "涓嬭厰娓╁害1", "涓嬭厰娓╁害2",
+			"涓嬭厰娓╁害3", "涓嬭厰娓╁害4", "涓嬭厰娓╁害5", "涓嬭厰娓╁害6"
 		}},
-		{EQ_ID_VACUUMBAKE, {
-			"A腔真空规值", "A腔温控1", "A腔温控2", "A腔温控4",
-			"A腔温控5", "A腔温控6", "A腔温控7", "B腔真空规值",
-			"B腔温控1", "B腔温控2", "B腔温控4", "B腔温控5",
-			"B腔温控6", "B腔温控7"
+		{MID_VacuumBakeA, {
+			"鐪熺┖瑙勫��", "娓╂帶1", "娓╂帶2", "娓╂帶4",
+			"娓╂帶5", "娓╂帶6", "娓╂帶7"
 		}},
-		{EQ_ID_BAKE_COOLING, {
-			"A烘烤温控1", "A烘烤温控2", "A烘烤温控4", "A烘烤温控5",
-			"A烘烤温控6", "A烘烤温控7", "B烘烤温控1", "B烘烤温控2",
-			"B烘烤温控4", "B烘烤温控5", "B烘烤温控6", "B烘烤温控7"
+		{MID_VacuumBakeB, {
+			"鐪熺┖瑙勫��", "娓╂帶1", "娓╂帶2", "娓╂帶4",
+			"娓╂帶5", "娓╂帶6", "娓╂帶7"
+		}},
+		{MID_BakeCoolingA, {
+			"鐑樼儰娓╂帶1", "鐑樼儰娓╂帶2", "鐑樼儰娓╂帶4", "鐑樼儰娓╂帶5",
+			"鐑樼儰娓╂帶6", "鐑樼儰娓╂帶7"
+		}},
+		{MID_BakeCoolingB, {
+			"鐑樼儰娓╂帶1", "鐑樼儰娓╂帶2", "鐑樼儰娓╂帶4", "鐑樼儰娓╂帶5",
+			"鐑樼儰娓╂帶6", "鐑樼儰娓╂帶7"
 		}}
 	};
 
@@ -98,8 +103,8 @@
 		}
 
 		if (eqid == EQ_ID_VACUUMBAKE) {
-			if (unit == 0) return "烘烤A腔";
-			if (unit == 1) return "烘烤B腔";
+			if (unit == 0) return "鐑樼儰A鑵�";
+			if (unit == 1) return "鐑樼儰B鑵�";
 		}
 
 		if (eqid == EQ_ID_Bonder1) {
@@ -112,10 +117,10 @@
 
 		if (eqid == EQ_ID_BAKE_COOLING) {
 
-			if (unit == 0) return "后烘烤A腔";
-			if (unit == 1) return "冷却A";
-			if (unit == 2) return "后烘烤B腔";
-			if (unit == 3) return "冷却B";
+			if (unit == 0) return "鍚庣儤鐑鑵�";
+			if (unit == 1) return "鍐峰嵈A";
+			if (unit == 2) return "鍚庣儤鐑鑵�";
+			if (unit == 3) return "鍐峰嵈B";
 		}
 
 		if (eqid == EQ_ID_MEASUREMENT) {
@@ -156,11 +161,11 @@
 
 		if (eqid == EQ_ID_VACUUMBAKE) {
 			if (unit == 0) {
-				sprintf_s(szBuffer, 256, "烘烤A腔(Slot%d)", slot);
+				sprintf_s(szBuffer, 256, "鐑樼儰A鑵�(Slot%d)", slot);
 				return std::string(szBuffer);
 			}
 			if (unit == 1) {
-				sprintf_s(szBuffer, 256, "烘烤B腔(Slot%d)", slot);
+				sprintf_s(szBuffer, 256, "鐑樼儰B鑵�(Slot%d)", slot);
 				return std::string(szBuffer);
 			}
 		}
@@ -177,10 +182,10 @@
 
 		if (eqid == EQ_ID_BAKE_COOLING) {
 
-			if (slot == 0) return "后烘烤A腔";
-			if (slot == 1) return "冷却A";
-			if (slot == 2) return "后烘烤B腔";
-			if (slot == 3) return "冷却B";
+			if (slot == 0) return "鍚庣儤鐑鑵�";
+			if (slot == 1) return "鍐峰嵈A";
+			if (slot == 2) return "鍚庣儤鐑鑵�";
+			if (slot == 3) return "鍐峰嵈B";
 		}
 
 		if (eqid == EQ_ID_MEASUREMENT) {
diff --git a/SourceCode/Bond/Servo/CServoUtilsTool.h b/SourceCode/Bond/Servo/CServoUtilsTool.h
index 2945984..06b167c 100644
--- a/SourceCode/Bond/Servo/CServoUtilsTool.h
+++ b/SourceCode/Bond/Servo/CServoUtilsTool.h
@@ -1,9 +1,16 @@
-#pragma once
+锘�#pragma once
 #include "ServoCommo.h"
 #include "CGlass.h"
 
 
 namespace SERVO {
+	constexpr uint32_t MID_Bonder1 = 1001;
+	constexpr uint32_t MID_Bonder2 = 1002;
+	constexpr uint32_t MID_VacuumBakeA = 1003;
+	constexpr uint32_t MID_VacuumBakeB = 1004;
+	constexpr uint32_t MID_BakeCoolingA = 1005;
+	constexpr uint32_t MID_BakeCoolingB = 1006;
+
 	class CServoUtilsTool
 	{
 	public:
diff --git a/SourceCode/Bond/Servo/CUserEdit2Dlg.cpp b/SourceCode/Bond/Servo/CUserEdit2Dlg.cpp
new file mode 100644
index 0000000..7237f5d
--- /dev/null
+++ b/SourceCode/Bond/Servo/CUserEdit2Dlg.cpp
@@ -0,0 +1,125 @@
+锘�#include "stdafx.h"
+#include "CUserEdit2Dlg.h"
+#include "CUserManager2.h"
+#include "resource.h"
+
+IMPLEMENT_DYNAMIC(CUserEdit2Dlg, CDialogEx)
+
+CUserEdit2Dlg::CUserEdit2Dlg(bool editMode, CWnd* pParent /*=nullptr*/)
+	: CDialogEx(IDD_DIALOG_USER_EDIT2, pParent)
+{
+	m_bEditMode = editMode;
+}
+
+CUserEdit2Dlg::~CUserEdit2Dlg()
+{
+}
+
+void CUserEdit2Dlg::DoDataExchange(CDataExchange* pDX)
+{
+	CDialogEx::DoDataExchange(pDX);
+	DDX_Text(pDX, IDC_EDIT_USER_ACCOUNT, m_strUsername);
+	DDX_Text(pDX, IDC_EDIT_USER_DISPLAY, m_strDisplayName);
+	DDX_Text(pDX, IDC_EDIT_USER_PASSWORD, m_strPassword);
+	DDX_CBString(pDX, IDC_COMBO_USER_ROLE, m_strRole);
+	DDX_Check(pDX, IDC_CHECK_USER_ENABLED, m_bEnabled);
+}
+
+BEGIN_MESSAGE_MAP(CUserEdit2Dlg, CDialogEx)
+END_MESSAGE_MAP()
+
+BOOL CUserEdit2Dlg::OnInitDialog()
+{
+	CDialogEx::OnInitDialog();
+
+	if (m_bEditMode) {
+		if (auto pEdit = GetDlgItem(IDC_EDIT_USER_ACCOUNT)) {
+			pEdit->EnableWindow(FALSE);
+		}
+	}
+
+	UpdateData(FALSE);
+
+	auto roles = CUserManager2::getInstance().getRoles();
+	CComboBox* pCombo = (CComboBox*)GetDlgItem(IDC_COMBO_USER_ROLE);
+	if (pCombo) {
+		int selected = -1;
+		for (const auto& role : roles) {
+			CString text(role.name.c_str());
+			int idx = pCombo->AddString(text);
+			if (selected == -1 && m_strRole.CompareNoCase(text) == 0) {
+				selected = idx;
+			}
+		}
+
+		if (selected >= 0) {
+			pCombo->SetCurSel(selected);
+		}
+		else if (pCombo->GetCount() > 0) {
+			pCombo->SetCurSel(0);
+			CString text;
+			pCombo->GetLBText(0, text);
+			if (m_strRole.IsEmpty()) {
+				m_strRole = text;
+			}
+		}
+	}
+
+	if (auto pPwd = GetDlgItem(IDC_EDIT_USER_PASSWORD)) {
+		pPwd->EnableWindow(!m_bEditMode);
+		if (m_bEditMode) {
+			pPwd->SetWindowText(_T(""));
+		}
+	}
+
+	return TRUE;
+}
+
+void CUserEdit2Dlg::OnOK()
+{
+	UpdateData(TRUE);
+
+	CString user = m_strUsername;
+	user.Trim();
+	CString role = m_strRole;
+	role.Trim();
+
+	CString password = m_strPassword;
+	password.Trim();
+
+	if (m_bEditMode) {
+		password.Empty();
+	}
+
+	if (!m_bEditMode) {
+		if (user.IsEmpty()) {
+			AfxMessageBox(_T("璇疯緭鍏ヨ处鍙�"));
+			return;
+		}
+
+		if (password.IsEmpty()) {
+			AfxMessageBox(_T("璇疯緭鍏ュ瘑鐮�"));
+			return;
+		}
+	}
+
+	if (role.IsEmpty()) {
+		AfxMessageBox(_T("璇烽�夋嫨瑙掕壊"));
+		return;
+	}
+
+	if (auto pCombo = (CComboBox*)GetDlgItem(IDC_COMBO_USER_ROLE)) {
+		int sel = pCombo->GetCurSel();
+		if (sel != CB_ERR) {
+			CString text;
+			pCombo->GetLBText(sel, text);
+			if (!text.IsEmpty()) {
+				m_strRole = text;
+			}
+		}
+	}
+
+	m_strUsername = user;
+	m_strPassword = password;
+	CDialogEx::OnOK();
+}
diff --git a/SourceCode/Bond/Servo/CUserEdit2Dlg.h b/SourceCode/Bond/Servo/CUserEdit2Dlg.h
new file mode 100644
index 0000000..e61e745
--- /dev/null
+++ b/SourceCode/Bond/Servo/CUserEdit2Dlg.h
@@ -0,0 +1,23 @@
+#pragma once
+
+class CUserEdit2Dlg : public CDialogEx
+{
+	DECLARE_DYNAMIC(CUserEdit2Dlg)
+
+public:
+	CUserEdit2Dlg(bool editMode = false, CWnd* pParent = nullptr);
+	virtual ~CUserEdit2Dlg();
+
+	CString m_strUsername;
+	CString m_strDisplayName;
+	CString m_strPassword;
+	CString m_strRole;
+	BOOL m_bEnabled = TRUE;
+	bool m_bEditMode = false;
+
+protected:
+	virtual void DoDataExchange(CDataExchange* pDX);
+	virtual BOOL OnInitDialog();
+	afx_msg void OnOK();
+	DECLARE_MESSAGE_MAP()
+};
diff --git a/SourceCode/Bond/Servo/CUserManager2.cpp b/SourceCode/Bond/Servo/CUserManager2.cpp
new file mode 100644
index 0000000..402b2f6
--- /dev/null
+++ b/SourceCode/Bond/Servo/CUserManager2.cpp
@@ -0,0 +1,250 @@
+锘�#include "stdafx.h"
+#include "CUserManager2.h"
+#include "ToolUnits.h"
+#include <vector>
+#include <map>
+#include <utility>
+#include <algorithm>
+#include <sstream>
+#include <cwchar>
+
+std::vector<std::wstring> SplitLines(const std::wstring& text) 
+{
+	std::wstringstream ss(text); std::vector<std::wstring> v; std::wstring line; while (std::getline(ss, line)) v.push_back(line); return v;
+}
+
+std::vector<std::wstring> SplitByDelimiter(const std::wstring& text, wchar_t delimiter)
+{
+	std::vector<std::wstring> parts;
+	size_t start = 0;
+	while (start <= text.length()) {
+		size_t pos = text.find(delimiter, start);
+		if (pos == std::wstring::npos) {
+			parts.push_back(text.substr(start));
+			break;
+		}
+
+		parts.push_back(text.substr(start, pos - start));
+		start = pos + 1;
+	}
+
+	return parts;
+}
+
+template<typename Fn>
+std::wstring ReadBufferVia(Fn fn)
+{
+	int need = fn(nullptr, 0); if (need <= 0) return L"";
+	std::wstring buf; buf.resize((size_t)need);
+	int rc = fn(buf.data(), need);
+	if (rc == 0) { if (!buf.empty() && buf.back() == L'\0') buf.pop_back(); return buf; }
+	return L"";
+}
+
+// 鑾峰彇鍗曚緥瀹炰緥
+CUserManager2& CUserManager2::getInstance() {
+	static CUserManager2 instance;
+	return instance;
+}
+
+CUserManager2::CUserManager2()
+{
+
+}
+
+CUserManager2::~CUserManager2()
+{
+
+}
+
+void CUserManager2::init(const char* pszDir)
+{
+	std::wstring dir = CToolUnits::AnsiToWString(std::string(pszDir));
+	UX_Init(dir.c_str());
+
+	wchar_t buffer[1024];
+	UX_GetUsers(buffer, 1024);
+	bool hasAny = false;
+	for (auto& ln : SplitLines(buffer)) { if (!ln.empty()) { hasAny = true; break; } }
+	if (!hasAny) {
+		const wchar_t* roles = L"Admin:100\nEE:80\nPE:50\nOperator:10\n";
+		(void)UX_SetRoleDefinitions(roles);
+		(void)UX_AddUser(L"admin", L"Administrator", L"admin123", L"Admin");
+
+		UX_DefineAction(L"start", L"鍚姩鏈哄彴", L"Operator"); 
+		UX_DefineAction(L"stop", L"鍋滄満", L"Operator");
+		UX_DefineAction(L"recipe", L"缂栬緫閰嶆柟", L"PE");
+		UX_DefineAction(L"delVarialbles", L"鍒犻櫎鍙橀噺", L"PE");
+		UX_DefineAction(L"addVarialbles", L"鏂板鍙橀噺", L"PE");
+		UX_DefineAction(L"editVarialbles", L"缂栬緫鍙橀噺", L"PE");
+		UX_DefineAction(L"addReports", L"鏂板Report", L"PE");
+		UX_DefineAction(L"editReports", L"缂栬緫Report", L"PE");
+		UX_DefineAction(L"delReports", L"鍒犻櫎Report", L"PE");
+		UX_DefineAction(L"addEvents", L"鏂板Event", L"PE");
+		UX_DefineAction(L"editEvents", L"缂栬緫Event", L"PE");
+		UX_DefineAction(L"delEvents", L"鍒犻櫎Event", L"PE");
+	}
+	// 纭繚鏉冮檺瀹氫箟瀛樺湪锛堝箓绛夛級
+	UX_DefineAction(L"addVarialbles", L"鏂板鍙橀噺", L"PE");
+	UX_DefineAction(L"editVarialbles", L"缂栬緫鍙橀噺", L"PE");
+	UX_DefineAction(L"delVarialbles", L"鍒犻櫎鍙橀噺", L"PE");
+	UX_DefineAction(L"addReports", L"鏂板Report", L"PE");
+	UX_DefineAction(L"editReports", L"缂栬緫Report", L"PE");
+	UX_DefineAction(L"delReports", L"鍒犻櫎Report", L"PE");
+	UX_DefineAction(L"delEvents", L"鍒犻櫎Event", L"PE");
+	UX_DefineAction(L"addEvents", L"鏂板Event", L"PE");
+	UX_DefineAction(L"editEvents", L"缂栬緫Event", L"PE");
+}
+
+bool CUserManager2::login(const char* pszAccount, const char* pszPwd)
+{
+	std::wstring strUser, strPwd;
+	strUser = CToolUnits::AnsiToWString(std::string(pszAccount));
+	strPwd = CToolUnits::AnsiToWString(std::string(pszPwd));
+	int rc = UX_Login(strUser.c_str(), strPwd.c_str());
+	return rc == UX_OK;
+}
+
+bool CUserManager2::isLoggedIn()
+{
+	return UX_IsLoggedIn();
+}
+
+std::string CUserManager2::getCurrentUserName()
+{
+	std::string strName;
+
+	int need = UX_GetCurrentUser(nullptr, 0);
+	std::wstring buf; buf.resize((size_t)need);
+	if (UX_GetCurrentUser(buf.data(), need) == UX_OK) { 
+		if (!buf.empty() && buf.back() == L'\0')
+			buf.pop_back(); 
+
+		strName = CToolUnits::WStringToAnsi(buf);
+	}
+
+	return strName;
+}
+
+bool CUserManager2::IsAdminCurrent() 
+{
+	if (UX_IsLoggedIn() != 1) return false;
+	int need = UX_GetCurrentUser(nullptr, 0); if (need <= 0) return false;
+	std::wstring user; user.resize((size_t)need);
+	if (UX_GetCurrentUser(user.data(), need) != 0) return false;
+	if (!user.empty() && user.back() == L'\0') user.pop_back();
+	int maxLvl = 0; auto rolesTxt = ReadBufferVia([](wchar_t* b, int n) { return UX_GetRoles(b, n); });
+	for (auto& ln : SplitLines(rolesTxt)) { size_t p = ln.find(L':'); if (p != std::wstring::npos) { int lvl = _wtoi(ln.substr(p + 1).c_str()); if (lvl > maxLvl) maxLvl = lvl; } }
+	int myLvl = 0; auto usersTxt = ReadBufferVia([](wchar_t* b, int n) { return UX_GetUsers(b, n); });
+	for (auto& ln : SplitLines(usersTxt)) {
+		if (ln.empty()) continue; size_t p1 = ln.find(L','), p2 = ln.find(L',', p1 == std::wstring::npos ? 0 : p1 + 1), p3 = ln.find(L',', p2 == std::wstring::npos ? 0 : p2 + 1);
+		std::wstring name = (p1 == std::wstring::npos ? ln : ln.substr(0, p1)); if (name == user) { if (p2 != std::wstring::npos) { std::wstring lvlS = ln.substr(p2 + 1, (p3 == std::wstring::npos ? ln.size() : p3) - (p2 + 1)); myLvl = _wtoi(lvlS.c_str()); } break; }
+	}
+
+	return (maxLvl > 0) && (myLvl >= maxLvl);
+}
+
+std::vector<CUserManager2::RoleInfo> CUserManager2::getRoles()
+{
+	std::vector<RoleInfo> roles;
+	auto txt = ReadBufferVia([](wchar_t* b, int n) { return UX_GetRoles(b, n); });
+	if (txt.empty()) {
+		return roles;
+	}
+
+	for (auto& line : SplitLines(txt)) {
+		if (line.empty()) continue;
+		size_t pos = line.find(L':');
+		RoleInfo info;
+		info.name = (pos == std::wstring::npos) ? line : line.substr(0, pos);
+		if (pos != std::wstring::npos) {
+			info.level = _wtoi(line.substr(pos + 1).c_str());
+		}
+
+		if (!info.name.empty()) {
+			roles.push_back(std::move(info));
+		}
+	}
+
+	std::sort(roles.begin(), roles.end(), [](const RoleInfo& a, const RoleInfo& b) {
+		if (a.level == b.level) {
+			return a.name < b.name;
+		}
+		return a.level > b.level;
+	});
+
+	return roles;
+}
+std::vector<CUserManager2::UserInfo> CUserManager2::getUsers()
+{
+	std::vector<UserInfo> users;
+	auto txt = ReadBufferVia([](wchar_t* b, int n) { return UX_GetUsers(b, n); });
+	if (txt.empty()) {
+		return users;
+	}
+
+	std::map<int, std::wstring> roleMap;
+	for (auto& role : getRoles()) {
+		roleMap[role.level] = role.name;
+	}
+
+	for (auto& line : SplitLines(txt)) {
+		if (line.empty()) continue;
+		auto parts = SplitByDelimiter(line, L',');
+		UserInfo info;
+		if (!parts.empty()) info.userName = parts[0];
+		if (parts.size() > 1) info.displayName = parts[1];
+		if (parts.size() > 2) info.roleLevel = _wtoi(parts[2].c_str());
+		if (parts.size() > 3) info.enabled = (_wtoi(parts[3].c_str()) != 0);
+		auto it = roleMap.find(info.roleLevel);
+		if (it != roleMap.end()) {
+			info.roleName = it->second;
+		}
+		users.push_back(std::move(info));
+	}
+
+	return users;
+}
+
+int CUserManager2::addUser(const std::wstring& userName, const std::wstring& displayName,
+	const std::wstring& password, const std::wstring& roleName, bool enabled)
+{
+	int rc = UX_AddUser(userName.c_str(), displayName.c_str(), password.c_str(), roleName.c_str());
+	if (rc == UX_OK && !enabled) {
+		UX_EnableUser(userName.c_str(), 0);
+	}
+
+	return rc;
+}
+
+int CUserManager2::updateUser(const std::wstring& userName, const std::wstring& displayName,
+	const std::wstring& password, const std::wstring& roleName, bool enabled)
+{
+	const wchar_t* disp = displayName.empty() ? nullptr : displayName.c_str();
+	const wchar_t* pwd = password.empty() ? nullptr : password.c_str();
+	const wchar_t* role = roleName.empty() ? nullptr : roleName.c_str();
+	return UX_UpdateUser(userName.c_str(), disp, pwd, role, enabled ? 1 : 0);
+}
+
+int CUserManager2::deleteUser(const std::wstring& userName)
+{
+	return UX_DeleteUser(userName.c_str());
+}
+
+int CUserManager2::setUserEnabled(const std::wstring& userName, bool enabled)
+{
+	return UX_EnableUser(userName.c_str(), enabled ? 1 : 0);
+}
+
+int CUserManager2::resetPassword(const std::wstring& userName, const std::wstring& password)
+{
+	return UX_ResetPassword(userName.c_str(), password.c_str());
+}
+
+
+
+
+
+
+
+
diff --git a/SourceCode/Bond/Servo/CUserManager2.h b/SourceCode/Bond/Servo/CUserManager2.h
new file mode 100644
index 0000000..794f454
--- /dev/null
+++ b/SourceCode/Bond/Servo/CUserManager2.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include <string>
+#include <vector>
+
+class CUserManager2
+{
+public:
+	static CUserManager2& getInstance();
+	CUserManager2(const CUserManager2&) = delete;
+	CUserManager2& operator=(const CUserManager2&) = delete;
+
+	struct RoleInfo
+	{
+		std::wstring name;
+		int level = 0;
+	};
+
+	struct UserInfo
+	{
+		std::wstring userName;
+		std::wstring displayName;
+		std::wstring roleName;
+		int roleLevel = 0;
+		bool enabled = false;
+	};
+
+public:
+	void init(const char* pszDir);
+	bool login(const char* pszAccount, const char* pszPwd);
+	bool isLoggedIn();
+	std::string getCurrentUserName();
+	bool IsAdminCurrent();
+	std::vector<RoleInfo> getRoles();
+	std::vector<UserInfo> getUsers();
+	int addUser(const std::wstring& userName, const std::wstring& displayName,
+		const std::wstring& password, const std::wstring& roleName, bool enabled);
+	int updateUser(const std::wstring& userName, const std::wstring& displayName,
+		const std::wstring& password, const std::wstring& roleName, bool enabled);
+	int deleteUser(const std::wstring& userName);
+	int setUserEnabled(const std::wstring& userName, bool enabled);
+	int resetPassword(const std::wstring& userName, const std::wstring& password);
+
+private:
+	CUserManager2();
+	~CUserManager2();
+};
+
diff --git a/SourceCode/Bond/Servo/CUserManager2Dlg.cpp b/SourceCode/Bond/Servo/CUserManager2Dlg.cpp
new file mode 100644
index 0000000..84be473
--- /dev/null
+++ b/SourceCode/Bond/Servo/CUserManager2Dlg.cpp
@@ -0,0 +1,322 @@
+锘�// CUserManager2Dlg.cpp
+//
+
+#include "stdafx.h"
+#include "Servo.h"
+#include "CUserManager2Dlg.h"
+#include "afxdialogex.h"
+#include "CUserEdit2Dlg.h"
+#include "InputDialog.h"
+#include "resource.h"
+#include "ToolUnits.h"
+
+IMPLEMENT_DYNAMIC(CUserManager2Dlg, CDialogEx)
+
+CUserManager2Dlg::CUserManager2Dlg(CWnd* pParent)
+	: CDialogEx(IDD_DIALOG_USER_MANAGER2, pParent)
+{
+}
+
+CUserManager2Dlg::~CUserManager2Dlg()
+{
+}
+
+void CUserManager2Dlg::DoDataExchange(CDataExchange* pDX)
+{
+	CDialogEx::DoDataExchange(pDX);
+	DDX_Control(pDX, IDC_LIST1, m_listUsers);
+}
+
+BEGIN_MESSAGE_MAP(CUserManager2Dlg, CDialogEx)
+	ON_WM_SIZE()
+	ON_BN_CLICKED(IDC_BUTTON_ADD, &CUserManager2Dlg::OnBnClickedButtonAdd)
+	ON_BN_CLICKED(IDC_BUTTON_EDIT, &CUserManager2Dlg::OnBnClickedButtonEdit)
+	ON_BN_CLICKED(IDC_BUTTON_DEL, &CUserManager2Dlg::OnBnClickedButtonDel)
+	ON_BN_CLICKED(IDC_BUTTON_RESET_PWD, &CUserManager2Dlg::OnBnClickedButtonResetPwd)
+	ON_BN_CLICKED(IDC_BUTTON_ENABLE, &CUserManager2Dlg::OnBnClickedButtonEnable)
+	ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST1, &CUserManager2Dlg::OnLvnItemchangedUsers)
+END_MESSAGE_MAP()
+
+BOOL CUserManager2Dlg::OnInitDialog()
+{
+	CDialogEx::OnInitDialog();
+
+	InitList();
+	RefreshUserList();
+	UpdateButtonState();
+
+	return TRUE;
+}
+
+void CUserManager2Dlg::OnSize(UINT nType, int cx, int cy)
+{
+	CDialogEx::OnSize(nType, cx, cy);
+}
+
+void CUserManager2Dlg::InitList()
+{
+	DWORD dwStyle = m_listUsers.GetExtendedStyle();
+	m_listUsers.SetExtendedStyle(dwStyle | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
+	m_listUsers.InsertColumn(0, _T("璐﹀彿"), LVCFMT_LEFT, 90);
+	m_listUsers.InsertColumn(1, _T("鏄剧ず鍚�"), LVCFMT_LEFT, 100);
+	m_listUsers.InsertColumn(2, _T("瑙掕壊"), LVCFMT_LEFT, 80);
+	m_listUsers.InsertColumn(3, _T("绾у埆"), LVCFMT_LEFT, 60);
+	m_listUsers.InsertColumn(4, _T("鐘舵��"), LVCFMT_LEFT, 70);
+}
+
+void CUserManager2Dlg::RefreshUserList()
+{
+	CString selectedName;
+	int currentIndex = GetSelectedIndex();
+	if (currentIndex >= 0 && currentIndex < static_cast<int>(m_users.size())) {
+		selectedName = m_users[currentIndex].userName.c_str();
+	}
+
+	m_users = CUserManager2::getInstance().getUsers();
+	m_listUsers.DeleteAllItems();
+
+	for (size_t i = 0; i < m_users.size(); ++i) {
+		const auto& user = m_users[i];
+		CString account(user.userName.c_str());
+		CString display(user.displayName.c_str());
+		CString role(user.roleName.empty() ? L"-" : user.roleName.c_str());
+		CString level;
+		level.Format(_T("%d"), user.roleLevel);
+		CString state = user.enabled ? _T("鍚敤") : _T("绂佺敤");
+
+		int row = m_listUsers.InsertItem(static_cast<int>(i), account);
+		m_listUsers.SetItemText(row, 1, display);
+		m_listUsers.SetItemText(row, 2, role);
+		m_listUsers.SetItemText(row, 3, level);
+		m_listUsers.SetItemText(row, 4, state);
+	}
+
+	if (!selectedName.IsEmpty()) {
+		for (int i = 0; i < m_listUsers.GetItemCount(); ++i) {
+			if (selectedName.CompareNoCase(m_listUsers.GetItemText(i, 0)) == 0) {
+				m_listUsers.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
+				m_listUsers.EnsureVisible(i, FALSE);
+				break;
+			}
+		}
+	}
+	UpdateButtonState();
+}
+
+void CUserManager2Dlg::UpdateButtonState()
+{
+	int index = GetSelectedIndex();
+	BOOL hasSelection = (index >= 0);
+
+	auto enable = [&](int id, BOOL enableFlag) {
+		if (CWnd* p = GetDlgItem(id)) {
+			p->EnableWindow(enableFlag);
+		}
+	};
+
+	enable(IDC_BUTTON_EDIT, hasSelection);
+	enable(IDC_BUTTON_DEL, hasSelection);
+	enable(IDC_BUTTON_RESET_PWD, hasSelection);
+	enable(IDC_BUTTON_ENABLE, hasSelection);
+
+	CString toggleText = _T("绂佺敤/鍚敤");
+	if (const auto* user = GetSelectedUser()) {
+		toggleText = user->enabled ? _T("绂佺敤") : _T("鍚敤");
+		if (IsCurrentUser(*user)) {
+			enable(IDC_BUTTON_DEL, FALSE);
+			enable(IDC_BUTTON_ENABLE, FALSE);
+		}
+	}
+
+	SetDlgItemText(IDC_BUTTON_ENABLE, toggleText);
+}
+
+int CUserManager2Dlg::GetSelectedIndex() const
+{
+	if (!::IsWindow(m_listUsers.GetSafeHwnd())) {
+		return -1;
+	}
+	return m_listUsers.GetNextItem(-1, LVNI_SELECTED);
+}
+
+const CUserManager2::UserInfo* CUserManager2Dlg::GetSelectedUser() const
+{
+	int index = GetSelectedIndex();
+	if (index < 0 || index >= static_cast<int>(m_users.size())) {
+		return nullptr;
+	}
+	return &m_users[index];
+}
+
+std::wstring CUserManager2Dlg::ToWString(const CString& text) const
+{
+	CString trimmed(text);
+	trimmed.Trim();
+
+	std::string str((LPTSTR)(LPCTSTR)trimmed);
+	return CToolUnits::AnsiToWString(str);
+}
+
+void CUserManager2Dlg::ShowErrorMessage(const CString& action, int code)
+{
+	const wchar_t* detail = UX_ErrorMessage(code);
+	CString msg;
+	msg.Format(_T("%s: %s"), action.GetString(), detail ? detail : L"Unknown");
+	AfxMessageBox(msg, MB_ICONERROR);
+}
+
+bool CUserManager2Dlg::IsCurrentUser(const CUserManager2::UserInfo& info) const
+{
+	CString current(CUserManager2::getInstance().getCurrentUserName().c_str());
+	CString account(info.userName.c_str());
+	current.Trim();
+	account.Trim();
+	return !current.IsEmpty() && current.CompareNoCase(account) == 0;
+}
+
+void CUserManager2Dlg::OnBnClickedButtonAdd()
+{
+	CUserEdit2Dlg dlg(false, this);
+	if (dlg.DoModal() != IDOK) {
+		return;
+	}
+
+	CString account = dlg.m_strUsername;
+	account.Trim();
+	CString display = dlg.m_strDisplayName;
+	display.Trim();
+	if (display.IsEmpty()) {
+		display = account;
+	}
+	CString role = dlg.m_strRole;
+	role.Trim();
+	CString password = dlg.m_strPassword;
+	password.Trim();
+
+	int rc = CUserManager2::getInstance().addUser(ToWString(account), ToWString(display), ToWString(password), ToWString(role), dlg.m_bEnabled == TRUE);
+	if (rc == UX_OK) {
+		RefreshUserList();
+		AfxMessageBox(_T("鏂板鐢ㄦ埛鎴愬姛"));
+	}
+	else {
+		ShowErrorMessage(_T("鏂板鐢ㄦ埛澶辫触"), rc);
+	}
+}
+
+void CUserManager2Dlg::OnBnClickedButtonEdit()
+{
+	const auto* user = GetSelectedUser();
+	if (!user) {
+		AfxMessageBox(_T("璇烽�夋嫨鐢ㄦ埛"));
+		return;
+	}
+
+	CUserEdit2Dlg dlg(true, this);
+	dlg.m_strUsername = user->userName.c_str();
+	dlg.m_strDisplayName = user->displayName.c_str();
+	dlg.m_strRole = user->roleName.c_str();
+	dlg.m_bEnabled = user->enabled ? TRUE : FALSE;
+	if (dlg.DoModal() != IDOK) {
+		return;
+	}
+
+	CString display = dlg.m_strDisplayName;
+	display.Trim();
+	CString password = dlg.m_strPassword;
+	password.Trim();
+	CString role = dlg.m_strRole;
+	role.Trim();
+
+	int rc = CUserManager2::getInstance().updateUser(user->userName, ToWString(display), ToWString(password), ToWString(role), dlg.m_bEnabled == TRUE);
+	if (rc == UX_OK) {
+		RefreshUserList();
+		AfxMessageBox(_T("淇濆瓨鎴愬姛"));
+	}
+	else {
+		ShowErrorMessage(_T("淇濆瓨澶辫触"), rc);
+	}
+}
+
+void CUserManager2Dlg::OnBnClickedButtonDel()
+{
+	const auto* user = GetSelectedUser();
+	if (!user) {
+		AfxMessageBox(_T("璇烽�夋嫨鐢ㄦ埛"));
+		return;
+	}
+
+	if (IsCurrentUser(*user)) {
+		AfxMessageBox(_T("涓嶈兘鍒犻櫎褰撳墠鐧诲綍鐢ㄦ埛"));
+		return;
+	}
+
+	CString prompt;
+	prompt.Format(_T("纭畾鍒犻櫎鐢ㄦ埛 %s ?"), CString(user->userName.c_str()));
+	if (AfxMessageBox(prompt, MB_ICONQUESTION | MB_YESNO) != IDYES) {
+		return;
+	}
+
+	int rc = CUserManager2::getInstance().deleteUser(user->userName);
+	if (rc == UX_OK) {
+		RefreshUserList();
+		AfxMessageBox(_T("鍒犻櫎鎴愬姛"));
+	}
+	else {
+		ShowErrorMessage(_T("鍒犻櫎澶辫触"), rc);
+	}
+}
+
+void CUserManager2Dlg::OnBnClickedButtonResetPwd()
+{
+	const auto* user = GetSelectedUser();
+	if (!user) {
+		AfxMessageBox(_T("璇烽�夋嫨鐢ㄦ埛"));
+		return;
+	}
+
+	CInputDialog dlg(_T("閲嶇疆瀵嗙爜"), _T("璇疯緭鍏ユ柊瀵嗙爜:"), this);
+	if (dlg.DoModal() != IDOK) {
+		return;
+	}
+
+	CString password = dlg.GetInputText();
+	password.Trim();
+	if (password.IsEmpty()) {
+		AfxMessageBox(_T("瀵嗙爜涓嶈兘涓虹┖"));
+		return;
+	}
+
+	int rc = CUserManager2::getInstance().resetPassword(user->userName, ToWString(password));
+	if (rc == UX_OK) {
+		AfxMessageBox(_T("瀵嗙爜宸查噸缃�"));
+	}
+	else {
+		ShowErrorMessage(_T("閲嶇疆澶辫触"), rc);
+	}
+}
+
+void CUserManager2Dlg::OnBnClickedButtonEnable()
+{
+	const auto* user = GetSelectedUser();
+	if (!user) {
+		AfxMessageBox(_T("璇烽�夋嫨鐢ㄦ埛"));
+		return;
+	}
+
+	bool enable = !user->enabled;
+	int rc = CUserManager2::getInstance().setUserEnabled(user->userName, enable);
+	if (rc == UX_OK) {
+		RefreshUserList();
+		CString msg = enable ? _T("鐢ㄦ埛宸插惎鐢�") : _T("鐢ㄦ埛宸茬鐢�");
+		AfxMessageBox(msg);
+	}
+	else {
+		ShowErrorMessage(_T("鎿嶄綔澶辫触"), rc);
+	}
+}
+
+void CUserManager2Dlg::OnLvnItemchangedUsers(NMHDR* /*pNMHDR*/, LRESULT* pResult)
+{
+	UpdateButtonState();
+	*pResult = 0;
+}
diff --git a/SourceCode/Bond/Servo/CUserManager2Dlg.h b/SourceCode/Bond/Servo/CUserManager2Dlg.h
new file mode 100644
index 0000000..2a2507c
--- /dev/null
+++ b/SourceCode/Bond/Servo/CUserManager2Dlg.h
@@ -0,0 +1,45 @@
+锘�#pragma once
+
+#include "CUserManager2.h"
+#include <string>
+#include <vector>
+
+class CUserManager2Dlg : public CDialogEx
+{
+	DECLARE_DYNAMIC(CUserManager2Dlg)
+
+public:
+	CUserManager2Dlg(CWnd* pParent = nullptr);
+	virtual ~CUserManager2Dlg();
+
+#ifdef AFX_DESIGN_TIME
+	enum { IDD = IDD_DIALOG_USER_MANAGER2 };
+#endif
+
+protected:
+	virtual void DoDataExchange(CDataExchange* pDX);
+
+	DECLARE_MESSAGE_MAP()
+public:
+	virtual BOOL OnInitDialog();
+	afx_msg void OnSize(UINT nType, int cx, int cy);
+	afx_msg void OnBnClickedButtonAdd();
+	afx_msg void OnBnClickedButtonEdit();
+	afx_msg void OnBnClickedButtonDel();
+	afx_msg void OnBnClickedButtonResetPwd();
+	afx_msg void OnBnClickedButtonEnable();
+	afx_msg void OnLvnItemchangedUsers(NMHDR* pNMHDR, LRESULT* pResult);
+
+private:
+	CListCtrl m_listUsers;
+	std::vector<CUserManager2::UserInfo> m_users;
+
+	void InitList();
+	void RefreshUserList();
+	void UpdateButtonState();
+	int GetSelectedIndex() const;
+	const CUserManager2::UserInfo* GetSelectedUser() const;
+	std::wstring ToWString(const CString& text) const;
+	void ShowErrorMessage(const CString& action, int code);
+	bool IsCurrentUser(const CUserManager2::UserInfo& info) const;
+};
diff --git a/SourceCode/Bond/Servo/CUserXLogDlg.cpp b/SourceCode/Bond/Servo/CUserXLogDlg.cpp
new file mode 100644
index 0000000..58e8950
--- /dev/null
+++ b/SourceCode/Bond/Servo/CUserXLogDlg.cpp
@@ -0,0 +1,178 @@
+锘�#include "stdafx.h"
+#include "Servo.h"
+#include "CUserXLogDlg.h"
+#include "afxdialogex.h"
+#include <functional>
+#include <vector>
+#include <sstream>
+
+namespace
+{
+	std::wstring ReadBufferVia(const std::function<int(wchar_t*, int)>& fn)
+	{
+		int need = fn(nullptr, 0);
+		if (need <= 0) {
+			return L"";
+		}
+
+		std::wstring buffer;
+		buffer.resize(static_cast<size_t>(need));
+		if (fn(buffer.data(), need) == UX_OK) {
+			if (!buffer.empty() && buffer.back() == L'\0') {
+				buffer.pop_back();
+			}
+			return buffer;
+		}
+
+		return L"";
+	}
+
+	std::vector<std::wstring> SplitLines(const std::wstring& text)
+	{
+		std::vector<std::wstring> lines;
+		std::wstringstream ss(text);
+		std::wstring line;
+		while (std::getline(ss, line)) {
+			lines.push_back(line);
+		}
+		return lines;
+	}
+}
+
+IMPLEMENT_DYNAMIC(CUserXLogDlg, CDialogEx)
+
+CUserXLogDlg::CUserXLogDlg(CWnd* pParent)
+	: CDialogEx(IDD_DIALOG_USERX_LOG, pParent)
+{
+}
+
+CUserXLogDlg::~CUserXLogDlg()
+{
+}
+
+void CUserXLogDlg::DoDataExchange(CDataExchange* pDX)
+{
+	CDialogEx::DoDataExchange(pDX);
+	DDX_Control(pDX, IDC_LIST1, m_listLogs);
+}
+
+BEGIN_MESSAGE_MAP(CUserXLogDlg, CDialogEx)
+	ON_WM_SIZE()
+	ON_WM_DESTROY()
+END_MESSAGE_MAP()
+
+BOOL CUserXLogDlg::OnInitDialog()
+{
+	CDialogEx::OnInitDialog();
+
+	InitListCtrl();
+	RefreshLogs();
+	AdjustLayout();
+
+	return TRUE;
+}
+
+void CUserXLogDlg::InitListCtrl()
+{
+	DWORD dwStyle = m_listLogs.GetExtendedStyle();
+	m_listLogs.SetExtendedStyle(dwStyle | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
+	m_listLogs.InsertColumn(0, _T("鏃堕棿"), LVCFMT_LEFT, 180);
+	m_listLogs.InsertColumn(1, _T("鐢ㄦ埛"), LVCFMT_LEFT, 120);
+	m_listLogs.InsertColumn(2, _T("鍔ㄤ綔"), LVCFMT_LEFT, 120);
+	m_listLogs.InsertColumn(3, _T("鎻忚堪"), LVCFMT_LEFT, 200);
+}
+
+void CUserXLogDlg::RefreshLogs()
+{
+	m_logs.clear();
+	m_listLogs.DeleteAllItems();
+
+	auto allLogs = ReadBufferVia([](wchar_t* buffer, int size) {
+		return UX_QueryLogs(200, buffer, size);
+	});
+
+	for (auto& rawLine : SplitLines(allLogs)) {
+		if (rawLine.empty()) continue;
+
+		auto trim = [](std::wstring value) {
+			size_t first = value.find_first_not_of(L" \t\r\n");
+			size_t last = value.find_last_not_of(L" \t\r\n");
+			if (first == std::wstring::npos || last == std::wstring::npos) {
+				return std::wstring();
+			}
+			return value.substr(first, last - first + 1);
+		};
+
+		auto takeField = [&](size_t& cursor) {
+			if (cursor == std::wstring::npos || cursor >= rawLine.length()) {
+				return std::wstring();
+			}
+			size_t pos = rawLine.find(L',', cursor);
+			std::wstring part = (pos == std::wstring::npos) ? rawLine.substr(cursor) : rawLine.substr(cursor, pos - cursor);
+			cursor = (pos == std::wstring::npos) ? std::wstring::npos : pos + 1;
+			return trim(part);
+		};
+
+		size_t cursor = 0;
+		LogItem item;
+		item.time = takeField(cursor).c_str();
+		item.user = takeField(cursor).c_str();
+		item.action = takeField(cursor).c_str();
+		if (cursor != std::wstring::npos && cursor < rawLine.length()) {
+			item.detail = trim(rawLine.substr(cursor)).c_str();
+		}
+
+		m_logs.push_back(item);
+	}
+
+	for (size_t i = 0; i < m_logs.size(); ++i) {
+		const auto& log = m_logs[i];
+		int row = m_listLogs.InsertItem(static_cast<int>(i), log.time);
+		m_listLogs.SetItemText(row, 1, log.user);
+		m_listLogs.SetItemText(row, 2, log.action);
+		m_listLogs.SetItemText(row, 3, log.detail);
+	}
+}
+
+void CUserXLogDlg::AdjustLayout()
+{
+	if (!::IsWindow(m_listLogs.GetSafeHwnd())) {
+		return;
+	}
+
+	CRect rcClient;
+	GetClientRect(&rcClient);
+	const int margin = 7;
+
+	CRect rcList(margin, margin, rcClient.right - margin, rcClient.bottom - 40);
+	m_listLogs.MoveWindow(rcList);
+
+	auto moveButton = [&](int id, int order) {
+		if (CWnd* pBtn = GetDlgItem(id)) {
+			CRect rc;
+			pBtn->GetWindowRect(&rc);
+			ScreenToClient(&rc);
+			int width = rc.Width();
+			int height = rc.Height();
+			rc.left = rcClient.right - margin - width - order * (width + margin);
+			rc.right = rc.left + width;
+			rc.top = rcClient.bottom - margin - height;
+			rc.bottom = rc.top + height;
+			pBtn->MoveWindow(rc);
+		}
+	};
+
+	moveButton(IDOK, 1);
+	moveButton(IDCANCEL, 0);
+}
+
+void CUserXLogDlg::OnSize(UINT nType, int cx, int cy)
+{
+	CDialogEx::OnSize(nType, cx, cy);
+	AdjustLayout();
+}
+
+void CUserXLogDlg::OnDestroy()
+{
+	CDialogEx::OnDestroy();
+}
diff --git a/SourceCode/Bond/Servo/CUserXLogDlg.h b/SourceCode/Bond/Servo/CUserXLogDlg.h
new file mode 100644
index 0000000..e28a810
--- /dev/null
+++ b/SourceCode/Bond/Servo/CUserXLogDlg.h
@@ -0,0 +1,42 @@
+锘�#pragma once
+
+#include <vector>
+#include <string>
+
+class CUserXLogDlg : public CDialogEx
+{
+	DECLARE_DYNAMIC(CUserXLogDlg)
+
+public:
+	CUserXLogDlg(CWnd* pParent = nullptr);
+	virtual ~CUserXLogDlg();
+
+#ifdef AFX_DESIGN_TIME
+	enum { IDD = IDD_DIALOG_USERX_LOG };
+#endif
+
+protected:
+	virtual void DoDataExchange(CDataExchange* pDX);
+
+	DECLARE_MESSAGE_MAP()
+public:
+	virtual BOOL OnInitDialog();
+	afx_msg void OnSize(UINT nType, int cx, int cy);
+	afx_msg void OnDestroy();
+
+private:
+	struct LogItem
+	{
+		CString time;
+		CString user;
+		CString action;
+		CString detail;
+	};
+
+	CListCtrl m_listLogs;
+	std::vector<LogItem> m_logs;
+
+	void InitListCtrl();
+	void RefreshLogs();
+	void AdjustLayout();
+};
diff --git a/SourceCode/Bond/Servo/CVariable.h b/SourceCode/Bond/Servo/CVariable.h
index 597b84d..aa16b34 100644
--- a/SourceCode/Bond/Servo/CVariable.h
+++ b/SourceCode/Bond/Servo/CVariable.h
@@ -3,7 +3,7 @@
 
 
 namespace SERVO {
-	// 变量格式
+	// 鍙橀噺绫诲瀷
 	enum class SVFromat {
 		U1 = 0,
 		U2,
@@ -33,6 +33,9 @@
 		std::string getValue();
 		__int64 getIntValue();
 		std::vector<CVariable>& getVarsValue();
+		void setName(const char* pszName) { m_strName = pszName; }
+		void setFormat(const char* pszFmt) { m_format = toFormat(pszFmt); }
+		void setRemark(const char* pszRemark) { m_strRemark = pszRemark; }
 
 	private:
 		unsigned int m_nVarialbeId;
diff --git a/SourceCode/Bond/Servo/CVariableEditDlg2.cpp b/SourceCode/Bond/Servo/CVariableEditDlg2.cpp
new file mode 100644
index 0000000..657ec41
--- /dev/null
+++ b/SourceCode/Bond/Servo/CVariableEditDlg2.cpp
@@ -0,0 +1,89 @@
+锘�#include "stdafx.h"
+#include "CVariableEditDlg2.h"
+#include "resource.h"
+
+IMPLEMENT_DYNAMIC(CVariableEditDlg2, CDialogEx)
+
+CVariableEditDlg2::CVariableEditDlg2(const CString& title, int varId, const CString& type, const CString& name, const CString& remark, CWnd* pParent)
+	: CDialogEx(IDD_DIALOG_VARIABLE_EDIT2, pParent),
+	m_strTitle(title),
+	m_varId(varId),
+	m_strType(type),
+	m_strName(name),
+	m_strRemark(remark)
+{
+}
+
+CVariableEditDlg2::~CVariableEditDlg2()
+{
+}
+
+void CVariableEditDlg2::DoDataExchange(CDataExchange* pDX)
+{
+	CDialogEx::DoDataExchange(pDX);
+	DDX_Control(pDX, IDC_EDIT_VAR_ID, m_editId);
+	DDX_Control(pDX, IDC_COMBO_VAR_TYPE, m_cbType);
+	DDX_Control(pDX, IDC_EDIT_VAR_NAME, m_editName);
+	DDX_Control(pDX, IDC_EDIT_VAR_REMARK, m_editRemark);
+}
+
+BEGIN_MESSAGE_MAP(CVariableEditDlg2, CDialogEx)
+END_MESSAGE_MAP()
+
+BOOL CVariableEditDlg2::OnInitDialog()
+{
+	CDialogEx::OnInitDialog();
+
+	SetWindowText(m_strTitle);
+
+	CString strId;
+	strId.Format(_T("%d"), m_varId);
+	m_editId.SetWindowText(strId);
+	m_editId.SetReadOnly(TRUE);
+
+	m_cbType.ResetContent();
+	const TCHAR* fmts[] = { _T("U1"), _T("U2"), _T("I2"), _T("A20"), _T("A50"), _T("L") };
+	for (auto f : fmts) {
+		m_cbType.AddString(f);
+	}
+	if (!m_strType.IsEmpty()) {
+		m_cbType.SelectString(-1, m_strType);
+	}
+	else {
+		m_cbType.SetCurSel(0);
+	}
+
+	m_editName.SetWindowText(m_strName);
+	m_editRemark.SetWindowText(m_strRemark);
+
+	return TRUE;
+}
+
+void CVariableEditDlg2::OnOK()
+{
+	CString name, fmt, remark;
+	m_editName.GetWindowText(name);
+	m_cbType.GetWindowText(fmt);
+	m_editRemark.GetWindowText(remark);
+
+	fmt.MakeUpper();
+	if (name.IsEmpty()) {
+		AfxMessageBox(_T("鍚嶇О涓嶈兘涓虹┖"));
+		return;
+	}
+	if (fmt.IsEmpty()) {
+		AfxMessageBox(_T("绫诲瀷涓嶈兘涓虹┖"));
+		return;
+	}
+	if (fmt != _T("U1") && fmt != _T("U2") && fmt != _T("I2")
+		&& fmt != _T("A20") && fmt != _T("A50") && fmt != _T("L")) {
+		AfxMessageBox(_T("绫诲瀷蹇呴』鏄� U1/U2/I2/A20/A50/L"));
+		return;
+	}
+
+	m_strName = name;
+	m_strType = fmt;
+	m_strRemark = remark;
+
+	CDialogEx::OnOK();
+}
\ No newline at end of file
diff --git a/SourceCode/Bond/Servo/CVariableEditDlg2.h b/SourceCode/Bond/Servo/CVariableEditDlg2.h
new file mode 100644
index 0000000..4d09c05
--- /dev/null
+++ b/SourceCode/Bond/Servo/CVariableEditDlg2.h
@@ -0,0 +1,34 @@
+锘�#pragma once
+#include "afxdialogex.h"
+
+// 鍙橀噺缂栬緫瀵硅瘽妗嗭紙鏂板/缂栬緫鍏辩敤锛屼娇鐢ㄨ祫婧愭ā鏉匡級
+class CVariableEditDlg2 : public CDialogEx
+{
+	DECLARE_DYNAMIC(CVariableEditDlg2)
+
+public:
+	CVariableEditDlg2(const CString& title, int varId, const CString& type, const CString& name, const CString& remark, CWnd* pParent = nullptr);
+	virtual ~CVariableEditDlg2();
+
+	int GetVarId() const { return m_varId; }
+	CString GetTypeText() const { return m_strType; }
+	CString GetNameText() const { return m_strName; }
+	CString GetRemark() const { return m_strRemark; }
+
+protected:
+	virtual BOOL OnInitDialog() override;
+	virtual void DoDataExchange(CDataExchange* pDX) override;
+	afx_msg void OnOK();
+
+	DECLARE_MESSAGE_MAP()
+
+private:
+	CString m_strTitle;
+	int m_varId;
+	CString m_strType;
+	CString m_strName;
+	CString m_strRemark;
+
+	CEdit   m_editId, m_editName, m_editRemark;
+	CComboBox m_cbType;
+};
diff --git a/SourceCode/Bond/Servo/ClientListDlg.cpp b/SourceCode/Bond/Servo/ClientListDlg.cpp
index 500062e..1e8cd71 100644
--- a/SourceCode/Bond/Servo/ClientListDlg.cpp
+++ b/SourceCode/Bond/Servo/ClientListDlg.cpp
@@ -132,7 +132,7 @@
 	{
 		const ClientInfo& client = clients[i];
 		
-		int nItem = m_listClients.InsertItem(i, CString(client.ip.c_str()));
+		int nItem = m_listClients.InsertItem((int)i, CString(client.ip.c_str()));
 		m_listClients.SetItemText(nItem, 1, CString(std::to_string(client.port).c_str()));
 		m_listClients.SetItemText(nItem, 2, client.versionOk ? _T("姝e父") : _T("寮傚父"));
 		m_listClients.SetItemText(nItem, 3, CString(client.status.c_str()));
diff --git a/SourceCode/Bond/Servo/Common.h b/SourceCode/Bond/Servo/Common.h
index 3031752..41b56e7 100644
--- a/SourceCode/Bond/Servo/Common.h
+++ b/SourceCode/Bond/Servo/Common.h
@@ -19,7 +19,12 @@
 #define RX_CODE_MASTER_STATE_CHANGED	1011
 #define RX_CODE_EQ_ROBOT_TASK			1012
 #define RX_CODE_LOADPORT_STATUS_CHANGED	1014
+#define RX_CODE_CONTROL_STATE_CHANGED	1015
+#define RX_CODE_CONTROLJOB_CHANGED		1016
 
+/* 软件侧 ALID */
+#define ALID_SOFTWARE_PAUSE_EVENT		9000
+#define ALID_SOFTWARE_TEST_ALARM		9099
 
 /* Channel Name */
 #define MC_CHANNEL1_NAME		"McChannel1"
@@ -29,6 +34,7 @@
 #define APPDLG_BACKGROUND_COLOR				RGB(255, 255, 255)
 #define LOGDLG_BACKGROUND_COLOR				RGB(255, 255, 255)
 #define PANEL_MASTER_BACKGROUND_COLOR		RGB(255, 255, 255)
+#define PANEL_PRODUCTION_BACKGROUND_COLOR	RGB(255, 255, 255)
 #define PANEL_ATTRIBUTES_BACKGROUND_COLOR	RGB(255, 255, 255)
 #define PANEL_EQUIPMENT_BACKGROUND_COLOR	RGB(255, 255, 255)
 #define PAGE_GRPAH1_BACKGROUND_COLOR		RGB(255, 255, 255)
@@ -46,6 +52,8 @@
 #define STATUSBAR_BK_STARTING				RGB(58, 127, 78)
 #define STATUSBAR_BK_RUNNING				RGB(34, 177, 76)
 #define STATUSBAR_BK_ALARM					RGB(255, 127, 39)
+#define CIM_STATUS_BK_SELECTED				STATUSBAR_BK_RUNNING
+#define CIM_STATUS_BK_DISCONNECTED			STATUSBAR_BK_NORMAL
 
 /* LOG BTN */
 #define BTN_LOG_FRAME_NORMAL			RGB(88, 88, 88)
@@ -552,4 +560,25 @@
 
 
 /* PPID名字最大长度 */
-#define PPID_NAME_MAX			80
\ No newline at end of file
+#define PPID_NAME_MAX			80
+
+
+/* 解除警告 按钮 */
+#define BTN_ALARM_OFF_FRAME_NORMAL		RGB(88, 88, 88)
+#define BTN_ALARM_OFF_FRAME_HOVER		RGB(88, 88, 88)
+#define BTN_ALARM_OFF_FRAME_PRESS		RGB(88, 88, 88)
+#define BTN_ALARM_OFF_BKGND_NORMAL		RGB(255, 127, 39)
+#define BTN_ALARM_OFF_BKGND_HOVER		RGB(255, 157, 59)
+#define BTN_ALARM_OFF_BKGND_PRESS		RGB(255, 100, 29)
+
+/* 静音按钮 */
+#define BTN_SOUND_OFF_FRAME_NORMAL		RGB(88, 88, 88)
+#define BTN_SOUND_OFF_FRAME_HOVER		RGB(88, 88, 88)
+#define BTN_SOUND_OFF_FRAME_PRESS		RGB(88, 88, 88)
+#define BTN_SOUND_OFF_BKGND_NORMAL		RGB(255, 127, 39)
+#define BTN_SOUND_OFF_BKGND_HOVER		RGB(255, 157, 59)
+#define BTN_SOUND_OFF_BKGND_PRESS		RGB(255, 100, 29)
+#define BTN_SOUND_ON_BKGND_NORMAL		RGB(100, 200, 100)
+#define BTN_SOUND_ON_BKGND_HOVER		RGB(150, 250, 150)
+#define BTN_SOUND_ON_BKGND_PRESS		RGB(50, 150, 50)
+
diff --git a/SourceCode/Bond/Servo/Configuration.h b/SourceCode/Bond/Servo/Configuration.h
index 0bf2628..9c6aaf7 100644
--- a/SourceCode/Bond/Servo/Configuration.h
+++ b/SourceCode/Bond/Servo/Configuration.h
@@ -34,6 +34,13 @@
 	int getPortCassetteSnSeed(int port);
 	void setPortCassetteSnSeed(int port, int seed);
 
+	// Production shift settings
+	// Reads shift start times from ini.
+	// - [Production] DayShiftStart=HH:MM (default 08:00)
+	// - [Production] NightShiftStart=HH:MM (default DayShiftStart+12h)
+	// Returns TRUE if both values are valid (or derived); otherwise FALSE and falls back to defaults.
+	BOOL getProductionShiftStartMinutes(int& dayStartMinutes, int& nightStartMinutes);
+
 public:
 	void setP2RemoteEqReconnectInterval(int second);
 	int getP2RemoteEqReconnectInterval();
diff --git a/SourceCode/Bond/Servo/ConfigurationProduction.cpp b/SourceCode/Bond/Servo/ConfigurationProduction.cpp
new file mode 100644
index 0000000..3aa9bcc
--- /dev/null
+++ b/SourceCode/Bond/Servo/ConfigurationProduction.cpp
@@ -0,0 +1,75 @@
+#include "stdafx.h"
+#include "Configuration.h"
+
+#include <mutex>
+#include <string>
+#include <unordered_map>
+
+static bool TryParseHHMM(const std::string& text, int& outMinutes)
+{
+	int hour = 0;
+	int minute = 0;
+	if (sscanf_s(text.c_str(), "%d:%d", &hour, &minute) != 2) return false;
+	if (hour < 0 || hour >= 24) return false;
+	if (minute < 0 || minute >= 60) return false;
+	outMinutes = hour * 60 + minute;
+	return true;
+}
+
+BOOL CConfiguration::getProductionShiftStartMinutes(int& dayStartMinutes, int& nightStartMinutes)
+{
+	struct CachedShift {
+		BOOL ok = FALSE;
+		int day = 0;
+		int night = 0;
+		bool inited = false;
+	};
+
+	static std::mutex s_mtx;
+	static std::unordered_map<std::string, CachedShift> s_cache;
+
+	const std::string filePath((LPCSTR)(LPCTSTR)m_strFilepath);
+	{
+		std::lock_guard<std::mutex> g(s_mtx);
+		auto it = s_cache.find(filePath);
+		if (it != s_cache.end() && it->second.inited) {
+			dayStartMinutes = it->second.day;
+			nightStartMinutes = it->second.night;
+			return it->second.ok;
+		}
+	}
+
+	char buf[64] = {};
+	GetPrivateProfileStringA("Production", "DayShiftStart", "08:00", buf, (DWORD)sizeof(buf), m_strFilepath);
+	std::string dayStr(buf);
+
+	GetPrivateProfileStringA("Production", "NightShiftStart", "", buf, (DWORD)sizeof(buf), m_strFilepath);
+	std::string nightStr(buf);
+
+	const int kDefaultDay = 8 * 60;
+	const int kDefaultNight = 20 * 60;
+
+	bool okDay = TryParseHHMM(dayStr, dayStartMinutes);
+	bool okNight = false;
+	if (!nightStr.empty()) okNight = TryParseHHMM(nightStr, nightStartMinutes);
+
+	if (!okDay) dayStartMinutes = kDefaultDay;
+	if (!okNight) nightStartMinutes = (dayStartMinutes + 12 * 60) % (24 * 60);
+
+	if (dayStartMinutes == nightStartMinutes) {
+		dayStartMinutes = kDefaultDay;
+		nightStartMinutes = kDefaultNight;
+		{
+			std::lock_guard<std::mutex> g(s_mtx);
+			s_cache[filePath] = CachedShift{ FALSE, dayStartMinutes, nightStartMinutes, true };
+		}
+		return FALSE;
+	}
+
+	const BOOL ok = (okDay && (nightStr.empty() ? TRUE : okNight)) ? TRUE : FALSE;
+	{
+		std::lock_guard<std::mutex> g(s_mtx);
+		s_cache[filePath] = CachedShift{ ok, dayStartMinutes, nightStartMinutes, true };
+	}
+	return ok;
+}
diff --git a/SourceCode/Bond/Servo/EqsGraphWnd.cpp b/SourceCode/Bond/Servo/EqsGraphWnd.cpp
index 9aa534d..9e68a4c 100644
--- a/SourceCode/Bond/Servo/EqsGraphWnd.cpp
+++ b/SourceCode/Bond/Servo/EqsGraphWnd.cpp
@@ -1,4 +1,4 @@
-#include "stdafx.h"
+锘�#include "stdafx.h"
 #include "EqsGraphWnd.h"
 #include "ColorTransfer.h"
 #include "MapPosWnd.h"
@@ -64,6 +64,8 @@
 	m_nMagneticLinHoz = 0;
 	m_nMagneticLinVer = 0;
 	m_hFontTitle = nullptr;
+	m_nIndicatorSize = 10;
+	m_nIndicatorMargin = 3;
 
 }
 
@@ -86,7 +88,7 @@
 	wc.cbClsExtra = 0;
 	wc.cbWndExtra = 0;
 
-	// 注册窗口类
+	// 娉ㄥ唽绐楀彛绫�
 	return (::RegisterClass(&wc) != 0);
 }
 
@@ -157,6 +159,20 @@
 
 	m_crItemIdText[0] = CColorTransfer::ApproximateColor(m_crItemNameText[0], -0.3f);
 	m_crItemIdText[1] = CColorTransfer::ApproximateColor(m_crItemNameText[1], -0.3f);
+}
+
+void CEqsGraphWnd::SetIndicatorSize(int nSize)
+{
+	if (nSize > 0) {
+		m_nIndicatorSize = nSize;
+	}
+}
+
+void CEqsGraphWnd::SetIndicatorMargin(int nMargin)
+{
+	if (nMargin >= 0) {
+		m_nIndicatorMargin = nMargin;
+	}
 }
 
 void CEqsGraphWnd::EnableScroll(BOOL bEnable)
@@ -235,7 +251,7 @@
 }
 
 /*
- * 计算磁力线位置
+ * 璁$畻纾佸姏绾夸綅缃�
  */
 void CEqsGraphWnd::CalculateMagneticLine(EQITEM* pItem, LPRECT lprcItemRect, int &hoz, int &ver)
 {
@@ -243,7 +259,7 @@
 	ver = 0;
 #define MAGNETIC_DIS		10
 
-	// 检测是否接近或对齐
+	// 妫�娴嬫槸鍚︽帴杩戞垨瀵归綈
 	for (int i = 0; i < m_arItem.GetSize(); i++) {
 		EQITEM *pTemp = (EQITEM*)m_arItem.GetAt(i);
 		if (pTemp != pItem) {
@@ -287,10 +303,10 @@
 }
 
 /*
- * 取得In Pin的区域
+ * 鍙栧緱In Pin鐨勫尯鍩�
  * pItem -- EQITEM
- * lpRect -- 得到的Rect
- * 返回是否成功
+ * lpRect -- 寰楀埌鐨凴ect
+ * 杩斿洖鏄惁鎴愬姛
  */
 BOOL CEqsGraphWnd::GetItemRect(EQITEM* pItem, LPRECT lpRect)
 {
@@ -321,11 +337,11 @@
 }
 
 /*
- * 取得In Pin的区域
+ * 鍙栧緱In Pin鐨勫尯鍩�
  * pItem -- EQITEM
- * nPinIndex -- in pin索引
- * lpRect -- 得到的Rect
- * 返回是否成功
+ * nPinIndex -- in pin绱㈠紩
+ * lpRect -- 寰楀埌鐨凴ect
+ * 杩斿洖鏄惁鎴愬姛
  */
 BOOL CEqsGraphWnd::GetInPinRect(EQITEM* pItem, int nPinIndex, LPRECT lpRect)
 {
@@ -345,11 +361,11 @@
 }
 
 /*
- * 取得Out Pin的区域
+ * 鍙栧緱Out Pin鐨勫尯鍩�
  * pItem -- EQITEM
- * nPinIndex -- in pin索引
- * lpRect -- 得到的Rect
- * 返回是否成功
+ * nPinIndex -- in pin绱㈠紩
+ * lpRect -- 寰楀埌鐨凴ect
+ * 杩斿洖鏄惁鎴愬姛
  */
 BOOL CEqsGraphWnd::GetOutPinRect(EQITEM* pItem, int nPinIndex, LPRECT lpRect)
 {
@@ -368,11 +384,11 @@
 }
 
 /*
- * 取得Pin的Point
+ * 鍙栧緱Pin鐨凱oint
  * pItem -- EQITEM
- * nPinIndex -- in pin索引
- * lpRect -- 得到的Rect
- * 返回是否成功
+ * nPinIndex -- in pin绱㈠紩
+ * lpRect -- 寰楀埌鐨凴ect
+ * 杩斿洖鏄惁鎴愬姛
  */
 BOOL CEqsGraphWnd::GetPinPoint(PIN *pPin, LPPOINT lpPoint)
 {
@@ -453,7 +469,7 @@
 }
 
 /*
- * 清空PIN连接线缓存点,以便重新计算和绘制
+ * 娓呯┖PIN杩炴帴绾跨紦瀛樼偣锛屼互渚块噸鏂拌绠楀拰缁樺埗
  */
 void CEqsGraphWnd::ClearConnectedLinePoint(EQITEM*& pItem)
 {
@@ -479,14 +495,7 @@
 
 void CEqsGraphWnd::SetOnListener(EqsGraphListener& listener)
 {
-	m_listener.onConnectPin = listener.onConnectPin;
-	m_listener.onCheckConnectPin = listener.onCheckConnectPin;
-	m_listener.onDisconnectPin = listener.onDisconnectPin;
-	m_listener.onDeleteEqItem = listener.onDeleteEqItem;
-	m_listener.onEqItemPosChanged = listener.onEqItemPosChanged;
-	m_listener.onDblckEqItem = listener.onDblckEqItem;
-	m_listener.onRclickEqItem = listener.onRclickEqItem;
-	m_listener.onSelectEqItem = listener.onSelectEqItem;
+	m_listener = listener;
 }
 
 BOOL CEqsGraphWnd::SetCurSel(int nSel)
@@ -555,7 +564,7 @@
  */
 EQITEM* CEqsGraphWnd::AddItem(int id, CString strText, DWORD_PTR dwData, int nType/* = ITEM_NORMAL*/)
 {
-	// 需要计算一个新位置,不然全部重叠在一起
+	// 闇�瑕佽绠椾竴涓柊浣嶇疆锛屼笉鐒跺叏閮ㄩ噸鍙犲湪涓�璧�
 	int x, y;
 	x = (m_arItem.GetCount() % 4) * 218;
 	y = (m_arItem.GetCount() / 4) * 168;
@@ -755,7 +764,7 @@
 	return 0;
 }
 
-// 删除Item, 如果pin有连接,注意先断开
+// 鍒犻櫎Item, 濡傛灉pin鏈夎繛鎺ワ紝娉ㄦ剰鍏堟柇寮�
 int CEqsGraphWnd::DeleteItem(EQITEM* pItem)
 {
 	for (int i = 0; i < m_arItem.GetSize(); i++) {
@@ -795,7 +804,7 @@
 }
 
 /*
- * 设置子项的选中状态
+ * 璁剧疆瀛愰」鐨勯�変腑鐘舵��
  */
 void CEqsGraphWnd::SetItemSelectState(int nIndex, BOOL bSelect)
 {
@@ -828,14 +837,14 @@
 }
 
 /*
- * 检测坐标点所在的项
- * 返回项类型, 如HT_ITEM, HT_PIN, HT_LINE
- * pItem - 所在的EQITEM
- * pPin --所在的pin, 如果在连线上,表示所属pin, out pin;
+ * 妫�娴嬪潗鏍囩偣鎵�鍦ㄧ殑椤�
+ * 杩斿洖椤圭被鍨�, 濡侶T_ITEM, HT_PIN, HT_LINE
+ * pItem - 鎵�鍦ㄧ殑EQITEM
+ * pPin --鎵�鍦ㄧ殑pin, 濡傛灉鍦ㄨ繛绾夸笂锛岃〃绀烘墍灞瀙in, out pin锛�
  */
 int CEqsGraphWnd::HighTest(POINT pt, OUT EQITEM*& pItem, OUT PIN *& pPin)
 {
-	// 检测是否在某个子项
+	// 妫�娴嬫槸鍚﹀湪鏌愪釜瀛愰」
 	int nRet = HT_NOWHERE;
 	pItem = NULL;
 	pPin = NULL;
@@ -844,7 +853,7 @@
 		EQITEM *pTempItem = (EQITEM*)m_arItem.GetAt(i);
 		GetItemRect(pTempItem, &rcItem);
 		if (::PtInRect(&rcItem, pt)) {
-			// 在Item
+			// 鍦↖tem
 			pItem = pTempItem;
 			nRet = HT_ITEM;
 			break;
@@ -854,7 +863,7 @@
 			CPtrArray * pPins = (CPtrArray *)pTempItem->pInPins;
 			for (int j = 0; j < pPins->GetSize(); j++) {
 				if (GetInPinRect(pTempItem, j, &rcPin) && ::PtInRect(&rcPin, pt)) {
-					// 在in pin上
+					// 鍦╥n pin涓�
 					pPin = (PIN *)pPins->GetAt(j);
 					pItem = pTempItem;
 					nRet = HT_PIN;
@@ -866,15 +875,15 @@
 				pPins = (CPtrArray *)pTempItem->pOutPins;
 				for (int j = 0; j < pPins->GetSize(); j++) {
 					if (GetOutPinRect(pTempItem, j, &rcPin) && ::PtInRect(&rcPin, pt)) {
-						// 在out pin
+						// 鍦╫ut pin
 						pPin = (PIN *)pPins->GetAt(j);
 						pItem = pTempItem;
 						nRet = HT_PIN;
 						break;
 					}
 					else {
-						// 是否在pin连接线上,即判断点是否在线上
-						// 点到直线的距离公式(先通过p1,p2用两点式求出直线的表达式,再套距离公式);abs()为取绝对值函数,sqrt()为开根号函数
+						// 鏄惁鍦╬in杩炴帴绾夸笂,鍗冲垽鏂偣鏄惁鍦ㄧ嚎涓�
+						// 鐐瑰埌鐩寸嚎鐨勮窛绂诲叕寮忥紙鍏堥�氳繃p1,p2鐢ㄤ袱鐐瑰紡姹傚嚭鐩寸嚎鐨勮〃杈惧紡锛屽啀濂楄窛绂诲叕寮忥級锛沘bs()涓哄彇缁濆鍊煎嚱鏁帮紝sqrt()涓哄紑鏍瑰彿鍑芥暟
 						PIN *pTempPin = (PIN *)pPins->GetAt(j);
 						if (pTempPin->pConnectedPin != NULL && pTempPin->nLinePtCount > 1) {
 							for (int i = 0; i < pTempPin->nLinePtCount - 1; i++) {
@@ -907,7 +916,7 @@
 }
 
 /*
- * 绘制虚线框,代表正在拖动的item
+ * 缁樺埗铏氱嚎妗嗭紝浠h〃姝e湪鎷栧姩鐨刬tem
  */
 void CEqsGraphWnd::DrawDropItemRectangle(LPRECT lpRect1, LPRECT lpRect2)
 {
@@ -935,7 +944,7 @@
 }
 
 /*
- * 绘制磁吸线
+ * 缁樺埗纾佸惛绾�
  */
 void CEqsGraphWnd::DrawMagneticLine(LPRECT lprcClient, int nHozLine1, int nHozLine2, int nVerLine1, int nVerLine2)
 {
@@ -969,16 +978,16 @@
 }
 
 /*
- * 缓制Pin连接线
- * pBrush -- 画刷
- * pPen - 画笔
- * lpPt1, lpPt2 -- Pin脚的位置
- * lpRect1, lpRect2 -- 两个Item的Rect
+ * 缂撳埗Pin杩炴帴绾�
+ * pBrush -- 鐢诲埛
+ * pPen - 鐢荤瑪
+ * lpPt1, lpPt2 -- Pin鑴氱殑浣嶇疆
+ * lpRect1, lpRect2 -- 涓や釜Item鐨凴ect
  */
 void CEqsGraphWnd::DrawPinConnectedLine(Gdiplus::Graphics *pGraphics, Gdiplus::Brush *pBrush, Gdiplus::Pen *pPen, LPPOINT lpPt1, LPPOINT lpPt2,
 	LPRECT lpRect1, LPRECT lpRect2, PIN *pOwnerPin)
 {
-	// 如果没有缓存线条的POINT,则先计算并缓存
+	// 濡傛灉娌℃湁缂撳瓨绾挎潯鐨凱OINT锛屽垯鍏堣绠楀苟缂撳瓨
 	ASSERT(pOwnerPin);
 
 	int nPinCount = ((CPtrArray*)pOwnerPin->pItem->pOutPins)->GetSize();
@@ -987,10 +996,10 @@
 	int nMargin = 12;
 	int x1, x2, y1;
 
-	if (pOwnerPin->nLinePtCount == 0) {						// 第一个点的最小折线长
+	if (pOwnerPin->nLinePtCount == 0) {						// 绗竴涓偣鐨勬渶灏忔姌绾块暱
 		::OffsetRect(lpRect1, +m_nOffsetX, +m_nOffsetY);
 		::OffsetRect(lpRect2, +m_nOffsetX, +m_nOffsetY);
-		lpPt1->x += m_nOffsetX;				// 消除偏移
+		lpPt1->x += m_nOffsetX;				// 娑堥櫎鍋忕Щ
 		lpPt1->y += m_nOffsetY;
 		lpPt2->x += m_nOffsetX;
 		lpPt2->y += m_nOffsetY;
@@ -1099,7 +1108,7 @@
 }
 
 /*
- * WindowProc,窗口过程
+ * WindowProc锛岀獥鍙h繃绋�
  */
 LRESULT CALLBACK CEqsGraphWnd::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
 {
@@ -1110,7 +1119,7 @@
 	}
 
 
-	// 处理窗口消息
+	// 澶勭悊绐楀彛娑堟伅
 	ASSERT(hWnd);
 	switch (uMsg)
 	{
@@ -1174,7 +1183,7 @@
 
 /*
  * WM_NCCREATE
- * 窗口创建
+ * 绐楀彛鍒涘缓
  */
 LRESULT CEqsGraphWnd::OnNcCreate(HWND hWnd, WPARAM wParam, LPARAM lParam)
 {
@@ -1187,7 +1196,7 @@
 
 /*
  * WM_DESTROY
- * 窗口销毁
+ * 绐楀彛閿�姣�
  */
 LRESULT CEqsGraphWnd::OnDestroy(WPARAM wParam, LPARAM lParam)
 {
@@ -1250,7 +1259,7 @@
 
 /*
  * WM_MOUSEMOVE
- * 鼠标滚动
+ * 榧犳爣婊氬姩
  */
 LRESULT CEqsGraphWnd::OnMouseMove(WPARAM wParam, LPARAM lParam)
 {
@@ -1259,7 +1268,7 @@
 
 /*
  * WM_LBUTTONDOWN
- * 鼠标左键按下
+ * 榧犳爣宸﹂敭鎸変笅
  */
 LRESULT CEqsGraphWnd::OnLButtonDown(WPARAM wParam, LPARAM lParam)
 {
@@ -1276,7 +1285,7 @@
 	int nLastVerLine = 0;
 
 
-	// 检测点击坐标是否在某一子项上,如是,则高亮显示
+	// 妫�娴嬬偣鍑诲潗鏍囨槸鍚﹀湪鏌愪竴瀛愰」涓婏紝濡傛槸锛屽垯楂樹寒鏄剧ず
 	EQITEM* pLastItem = m_pCurItem;
 	PIN *pLastPin = m_pCurPin;
 	PIN *pLastSelLineOutPin = m_pSelLineOutPin;
@@ -1323,14 +1332,14 @@
 	bChanged = pLastItem != m_pCurItem || pLastPin != m_pCurPin || pLastSelLineOutPin != m_pSelLineOutPin;
 
 
-	// 刷新
+	// 鍒锋柊
 	SetFocus(m_hWnd);
 	if (bChanged) {
 		::InvalidateRect(m_hWnd, &rcClient, TRUE);
 	}
 
 
-	// 捕捉鼠标消息,检测是否拖动
+	// 鎹曟崏榧犳爣娑堟伅锛屾娴嬫槸鍚︽嫋鍔�
 	if (nRet == HT_ITEM && m_pCurItem != NULL) {
 		GetItemRect(m_pCurItem, &rcItem);
 
@@ -1400,7 +1409,7 @@
 	}
 
 
-	// 捕捉鼠标消息,检测是否连接引脚
+	// 鎹曟崏榧犳爣娑堟伅锛屾娴嬫槸鍚﹁繛鎺ュ紩鑴�
 	else if (nRet == HT_PIN && m_pCurPin != NULL) {
 		if (::GetCapture() == NULL) {
 			BOOL bLast = FALSE;
@@ -1427,12 +1436,12 @@
 					ptNew = msg.pt;
 					::ScreenToClient(m_hWnd, &ptNew);
 
-					// 擦除上一次
+					// 鎿﹂櫎涓婁竴娆�
 					if (bLast) {
 						DrawPinWillConnectLine(lineColor, &ptPin, &ptLast);
 					}
 
-					// 检测是否可以连接
+					// 妫�娴嬫槸鍚﹀彲浠ヨ繛鎺�
 					bCanConnect = false;
 					nRet = HighTest(ptNew, pHitItem, pHitPin);
 					if (nRet == HT_PIN) {
@@ -1458,12 +1467,12 @@
 					ptNew = msg.pt;
 					::ScreenToClient(m_hWnd, &ptNew);
 
-					// 擦除上一次
+					// 鎿﹂櫎涓婁竴娆�
 					if (bLast) {
 						DrawPinWillConnectLine(lineColor, &ptPin, &ptLast);
 					}
 
-					// 检测是否可以连接
+					// 妫�娴嬫槸鍚﹀彲浠ヨ繛鎺�
 					bCanConnect = false;
 					nRet = HighTest(ptNew, pHitItem, pHitPin);
 					if (nRet == HT_PIN) {
@@ -1498,7 +1507,7 @@
 	}
 
 	
-	// 检测鼠标消息,检测是否移动画布
+	// 妫�娴嬮紶鏍囨秷鎭紝妫�娴嬫槸鍚︾Щ鍔ㄧ敾甯�
 	else if (nRet == HT_NOWHERE) {
 		if (::GetCapture() == NULL) {
 			int nLastOffsetX = m_nOffsetX;
@@ -1569,7 +1578,7 @@
 
 /*
  * WM_LBUTTONDBLCLK
- * 鼠标左键双击
+ * 榧犳爣宸﹂敭鍙屽嚮
  */
 LRESULT CEqsGraphWnd::OnLButtonDblclk(WPARAM wParam, LPARAM lParam)
 {
@@ -1581,7 +1590,7 @@
 	GetClientRect(m_hWnd, &rcClient);
 	rcLast = { 0, 0, 0, 0 };
 
-	// 检测点击坐标是否在某一子项上,如是,则高亮显示
+	// 妫�娴嬬偣鍑诲潗鏍囨槸鍚﹀湪鏌愪竴瀛愰」涓婏紝濡傛槸锛屽垯楂樹寒鏄剧ず
 	EQITEM* pLastItem = m_pCurItem;
 	BOOL bChanged = FALSE;
 	EQITEM* pHitItem = NULL;
@@ -1601,7 +1610,7 @@
 
 /*
  * WM_MOUSEWHEEL
- * 鼠标滚动
+ * 榧犳爣婊氬姩
  */
 LRESULT CEqsGraphWnd::OnMouseWheel(WPARAM wParam, LPARAM lParam)
 {
@@ -1628,7 +1637,7 @@
 
 /*
 * WM_MOUSEHWHEEL
-* 鼠标滚动
+* 榧犳爣婊氬姩
 */
 LRESULT CEqsGraphWnd::OnMouseHWheel(WPARAM wParam, LPARAM lParam)
 {
@@ -1655,7 +1664,7 @@
 
 /*
  * WM_RBUTTONDOWN
- * 鼠标左键按下
+ * 榧犳爣宸﹂敭鎸変笅
  */
 LRESULT CEqsGraphWnd::OnRButtonDown(WPARAM wParam, LPARAM lParam)
 {
@@ -1667,7 +1676,7 @@
 	GetClientRect(m_hWnd, &rcClient);
 	rcLast = { 0, 0, 0, 0 };
 
-	// 检测点击坐标是否在某一子项上,如是,则高亮显示
+	// 妫�娴嬬偣鍑诲潗鏍囨槸鍚﹀湪鏌愪竴瀛愰」涓婏紝濡傛槸锛屽垯楂樹寒鏄剧ず
 	EQITEM* pLastItem = m_pCurItem;
 	PIN *pLastPin = m_pCurPin;
 	PIN *pLastSelLineOutPin = m_pSelLineOutPin;
@@ -1710,14 +1719,14 @@
 	bChanged = pLastItem != m_pCurItem || pLastPin != m_pCurPin || pLastSelLineOutPin != m_pSelLineOutPin;
 
 
-	// 刷新
+	// 鍒锋柊
 	SetFocus(m_hWnd);
 	if (bChanged) {
 		::InvalidateRect(m_hWnd, &rcClient, TRUE);
 	}
 
 
-	// 捕捉鼠标消息,检测是否拖动
+	// 鎹曟崏榧犳爣娑堟伅锛屾娴嬫槸鍚︽嫋鍔�
 	if (nRet == HT_ITEM && m_pCurItem != NULL) {
 		CopyRect(&rcItem, &m_pCurItem->rect);
 
@@ -1770,13 +1779,13 @@
 
 /*
  * WM_KEYDOWN
- * 键盘消息,按下按键
+ * 閿洏娑堟伅锛屾寜涓嬫寜閿�
  */
 LRESULT CEqsGraphWnd::OnKeyDown(WPARAM wParam, LPARAM lParam)
 {
 	BOOL bChanged = FALSE;
 	if (wParam == VK_DELETE) {
-		// 如果当前选择为线,则断开连接
+		// 濡傛灉褰撳墠閫夋嫨涓虹嚎锛屽垯鏂紑杩炴帴
 		if (m_pSelLineOutPin != NULL) {
 			if (m_listener.onDisconnectPin != nullptr) {
 				if (m_listener.onDisconnectPin(m_pSelLineOutPin)) {
@@ -1866,7 +1875,6 @@
 	CString strText;
 	HBRUSH hBrushBK;
 
-
 	// BeginPaint
 	PAINTSTRUCT ps;
 	hDC = BeginPaint(m_hWnd, &ps);
@@ -1877,14 +1885,12 @@
 		rcClient.bottom - rcClient.top);
 	::SelectObject(hMemDC, hBitmap);
 
-
-	// 背景颜色
+	// 鑳屾櫙棰滆壊
 	hBrushBK = CreateSolidBrush(m_crBkgnd);
 	::FillRect(hMemDC, &rcClient, hBrushBK);
 	DeleteObject(hBrushBK);
 
-
-	// 标题
+	// 鏍囬瀛椾綋
 	if (m_hFontTitle == nullptr) {
 		LOGFONT lf;
 		HFONT hFontDefault = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
@@ -1894,8 +1900,9 @@
 		m_hFontTitle = CreateFontIndirect(&lf);
 	}
 
+	// 缁樺埗鏍囬鏂囨湰
 	{
-		char szTitle[256];
+		char szTitle[256] = { 0 };
 		GetWindowText(m_hWnd, szTitle, 256);
 		RECT rcTitle;
 		rcTitle.left = rcClient.left + 5;
@@ -1906,21 +1913,23 @@
 		::DrawText(hMemDC, szTitle, (int)strlen(szTitle), &rcTitle, DT_LEFT | DT_TOP);
 	}
 
-
-	// 绘制子项
+	// 缁樺埗瀛愰」
 	HBRUSH hbrItemBackground[2];
 	HBRUSH hbrItemFrame[2];
 	HBRUSH hbrPinBackground[3];
 	HBRUSH hbrIndicator;
+	HBRUSH hbrIndicatorGray;
+
 	hbrItemBackground[0] = CreateSolidBrush(m_crItemBackground[0]);
 	hbrItemBackground[1] = CreateSolidBrush(m_crItemBackground[1]);
 	hbrItemFrame[0] = CreateSolidBrush(m_crItemFrame[0]);
 	hbrItemFrame[1] = CreateSolidBrush(m_crItemFrame[1]);
-	hbrIndicator = CreateSolidBrush(RGB(34, 177, 76));
+	hbrIndicator = CreateSolidBrush(RGB(34, 177, 76));    // 缁胯壊
+	hbrIndicatorGray = CreateSolidBrush(RGB(192, 192, 192));  // 鐏拌壊
+
 	for (int i = 0; i < 3; i++) {
 		hbrPinBackground[i] = CreateSolidBrush(m_crPinBkgnd[i]);
 	}
-
 
 	// gdi+
 	Gdiplus::Graphics graphics(hMemDC);
@@ -1931,21 +1940,22 @@
 		graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);
 	}
 
-
 	SetBkMode(hMemDC, TRANSPARENT);
 	{
 		RECT rcItem;
 		int nPinState;
 		int nItemCount = (int)m_arItem.GetCount();
+
+		// 鍏堢敾 item銆佹枃鏈�乸in 鍜屾寚绀虹伅
 		for (int i = 0; i < nItemCount; i++) {
 			EQITEM* pItem = (EQITEM*)m_arItem.GetAt(i);
 			if (pItem->nFlashFlag == 1) {
 				continue;
 			}
+
 			GetItemRect(pItem, &rcItem);
 
-
-			// 子项背景和边框
+			// 瀛愰」鑳屾櫙鍜岃竟妗�
 			if (m_nItemRound == 0) {
 				::FillRect(hMemDC, &rcItem, pItem->bHighlight ? hbrItemBackground[1] : hbrItemBackground[0]);
 				::FrameRect(hMemDC, &rcItem, pItem->bHighlight ? hbrItemFrame[1] : hbrItemFrame[0]);
@@ -1957,27 +1967,37 @@
 				::DeleteObject(hRgn);
 			}
 
-
-			// name和id
+			// name
 			HFONT hFontOld = (HFONT)::SelectObject(hMemDC, m_hFontName);
 			::SetTextColor(hMemDC, pItem->bHighlight ? m_crItemNameText[1] : m_crItemNameText[0]);
-			::DrawText(hMemDC, pItem->text, (int)strlen(pItem->text), &rcItem, DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
+			::DrawText(hMemDC, pItem->text, (int)strlen(pItem->text), &rcItem,
+				DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
 
-			
-			// 添加一个小绿点指示器
-			if(pItem->bShowIndicator[0]){
+			// indicators vertical column layout
+			const int indicatorSize = m_nIndicatorSize;
+			const int indicatorMargin = m_nIndicatorMargin;
+			const int indicatorX = rcItem.left + 5;
+
+			for (int k = 0; k < EQITEM_INDICATOR_COUNT; ++k) {
+				BYTE indicatorState = pItem->nIndicatorState[k];
+				if (indicatorState == INDICATOR_STATE_HIDDEN) {
+					continue;
+				}
+
 				RECT rcIndicator;
-				rcIndicator.left = rcItem.left + 5;
-				rcIndicator.top = rcItem.top + 5;
-				rcIndicator.right = rcIndicator.left + 12;
-				rcIndicator.bottom = rcIndicator.top + 12;
-				HRGN hRgn = CreateRoundRectRgn(rcIndicator.left, rcIndicator.top, rcIndicator.right, rcIndicator.bottom, 2, 2);
-				::FillRgn(hMemDC, hRgn, hbrIndicator);
-				::FrameRgn(hMemDC, hRgn, hbrItemFrame[0], 1, 1);
-				::DeleteObject(hRgn);
+				rcIndicator.left = indicatorX;
+				rcIndicator.top = rcItem.top + 5 + k * (indicatorSize + indicatorMargin);
+				rcIndicator.right = rcIndicator.left + indicatorSize;
+				rcIndicator.bottom = rcIndicator.top + indicatorSize;
+
+				RECT rcInner = rcIndicator;
+				::InflateRect(&rcInner, -1, -1);
+				::FillRect(hMemDC, &rcInner, indicatorState == INDICATOR_STATE_HIGHLIGHT
+					? hbrIndicator : hbrIndicatorGray);
+				::FrameRect(hMemDC, &rcIndicator, hbrItemFrame[0]);
 			}
 
-
+			// ID 鏂囨湰锛堥潪灏忓彿 item锛�
 			if (pItem->nShowType != ITEM_SMALL) {
 				RECT rcId = rcItem;
 				rcId.left += 5;
@@ -1986,29 +2006,28 @@
 				strId.Format(_T("ID:%d"), pItem->id);
 				::SelectObject(hMemDC, m_hFontId);
 				::SetTextColor(hMemDC, pItem->bHighlight ? m_crItemIdText[1] : m_crItemIdText[0]);
-				::DrawText(hMemDC, strId, (int)strId.GetLength(), &rcId, DT_LEFT | DT_BOTTOM | DT_SINGLELINE | DT_END_ELLIPSIS);
+				::DrawText(hMemDC, strId, (int)strId.GetLength(), &rcId,
+					DT_LEFT | DT_BOTTOM | DT_SINGLELINE | DT_END_ELLIPSIS);
 			}
 
-
-			// 动画效果不绘pin
+			// 鍔ㄧ敾鏁堟灉鏃朵笉缁樺埗 pin
 			if (m_pAnimationItem == pItem) {
+				::SelectObject(hMemDC, hFontOld);
 				continue;
 			}
 
-
-			// 绘制pin
+			// 缁樺埗 pin
 			RECT rcPin, rcPin2, rcPinText;
-			CPtrArray *pPins;
+			CPtrArray* pPins;
 			rcPinText.left = rcItem.left + 8;
 			rcPinText.right = rcItem.right - 8;
 
-
 			// in pins
-			PIN *pPin = NULL;
-			pPins = (CPtrArray *)pItem->pInPins;
+			PIN* pPin = NULL;
+			pPins = (CPtrArray*)pItem->pInPins;
 			for (int j = 0; j < pPins->GetSize(); j++) {
 				if (GetInPinRect(pItem, j, &rcPin)) {
-					pPin = (PIN *)pPins->GetAt(j);
+					pPin = (PIN*)pPins->GetAt(j);
 					::FrameRect(hMemDC, &rcPin, pItem->bHighlight ? hbrItemFrame[1] : hbrItemFrame[0]);
 
 					rcPin2.left = rcPin.left + 1;
@@ -2016,22 +2035,25 @@
 					rcPin2.top = rcPin.top + 1;
 					rcPin2.bottom = rcPin.bottom - 1;
 					nPinState = GetPinState(pPin);
-					::FillRect(hMemDC, &rcPin2, nPinState == 0 ? (pItem->bHighlight ? hbrItemBackground[1] : hbrItemBackground[0]) : hbrPinBackground[nPinState]);
+					::FillRect(hMemDC, &rcPin2,
+						nPinState == 0
+						? (pItem->bHighlight ? hbrItemBackground[1] : hbrItemBackground[0])
+						: hbrPinBackground[nPinState]);
 
 					if (pItem->nShowType != ITEM_SMALL) {
 						rcPinText.top = rcPin.top - 12;
 						rcPinText.bottom = rcPin.bottom + 12;
 						::SetTextColor(hMemDC, pItem->bHighlight ? m_crItemIdText[1] : m_crItemIdText[0]);
-						::DrawText(hMemDC, pPin->text, (int)strlen(pPin->text), &rcPinText, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
+						::DrawText(hMemDC, pPin->text, (int)strlen(pPin->text), &rcPinText,
+							DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
 					}
 				}
 			}
 
-
 			// out pins
-			pPins = (CPtrArray *)pItem->pOutPins;
+			pPins = (CPtrArray*)pItem->pOutPins;
 			for (int j = 0; j < pPins->GetSize(); j++) {
-				pPin = (PIN *)pPins->GetAt(j);
+				pPin = (PIN*)pPins->GetAt(j);
 				if (GetOutPinRect(pItem, j, &rcPin)) {
 					::FrameRect(hMemDC, &rcPin, pItem->bHighlight ? hbrItemFrame[1] : hbrItemFrame[0]);
 
@@ -2040,50 +2062,53 @@
 					rcPin2.top = rcPin.top + 1;
 					rcPin2.bottom = rcPin.bottom - 1;
 					nPinState = GetPinState(pPin);
-					::FillRect(hMemDC, &rcPin2, nPinState == 0 ? (pItem->bHighlight ? hbrItemBackground[1] : hbrItemBackground[0]) : hbrPinBackground[nPinState]);
+					::FillRect(hMemDC, &rcPin2,
+						nPinState == 0
+						? (pItem->bHighlight ? hbrItemBackground[1] : hbrItemBackground[0])
+						: hbrPinBackground[nPinState]);
 
 					if (pItem->nShowType != ITEM_SMALL) {
 						rcPinText.top = rcPin.top - 12;
 						rcPinText.bottom = rcPin.bottom + 12;
 						::SetTextColor(hMemDC, pItem->bHighlight ? m_crItemIdText[1] : m_crItemIdText[0]);
-						::DrawText(hMemDC, pPin->text, (int)strlen(pPin->text), &rcPinText, DT_RIGHT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
+						::DrawText(hMemDC, pPin->text, (int)strlen(pPin->text), &rcPinText,
+							DT_RIGHT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
 					}
 				}
 			}
 
-			::DeleteObject(hbrItemFrame);
 			::SelectObject(hMemDC, hFontOld);
 		}
 
-
-		// 绘制连接线,保存线条在最后绘制
+		// 鍐嶇粯鍒惰繛鎺ョ嚎
 		for (int i = 0; i < nItemCount; i++) {
-			EQITEM *pItem = (EQITEM*)m_arItem.GetAt(i);
+			EQITEM* pItem = (EQITEM*)m_arItem.GetAt(i);
 			if (pItem->nFlashFlag == 1) {
 				continue;
 			}
 
-			PIN *pPin = NULL;
-			CPtrArray *pPins;
-
-			// out pins边线
+			PIN* pPin = NULL;
+			CPtrArray* pPins;
 			RECT rcItem1, rcItem2;
-			pPins = (CPtrArray *)pItem->pOutPins;
+
+			// out pins 杈圭嚎
+			pPins = (CPtrArray*)pItem->pOutPins;
 			for (int j = 0; j < pPins->GetSize(); j++) {
-				pPin = (PIN *)pPins->GetAt(j);
+				pPin = (PIN*)pPins->GetAt(j);
 				if (pPin->pConnectedPin != NULL) {
 					POINT pt1, pt2;
 					if (GetPinPoint(pPin, &pt1) && GetPinPoint(pPin->pConnectedPin, &pt2)) {
 						GetItemRect(pItem, &rcItem1);
 						GetItemRect(pPin->pConnectedPin->pItem, &rcItem2);
-						DrawPinConnectedLine(&graphics, &brush1, pPin == m_pSelLineOutPin ? &pen2 : &pen1,
+						DrawPinConnectedLine(&graphics, &brush1,
+							pPin == m_pSelLineOutPin ? &pen2 : &pen1,
 							&pt1, &pt2, &rcItem1, &rcItem2, pPin);
 					}
 				}
 			}
 		}
 
-
+		// 鍒犻櫎 brush
 		for (int i = 0; i < 3; i++) {
 			::DeleteObject(hbrPinBackground[i]);
 		}
@@ -2091,11 +2116,9 @@
 		::DeleteObject(hbrItemBackground[1]);
 		::DeleteObject(hbrItemFrame[0]);
 		::DeleteObject(hbrItemFrame[1]);
-		::DeleteObject(hbrIndicator);		
+		::DeleteObject(hbrIndicator);
+		::DeleteObject(hbrIndicatorGray);
 	}
-
-
-
 
 	// EndPaint
 	::BitBlt(hDC, 0, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top,
@@ -2104,9 +2127,9 @@
 	::DeleteObject(hBitmap);
 	::DeleteDC(hMemDC);
 
-
 	return 1;
 }
+
 
 /*
  * WM_SIZE
@@ -2354,8 +2377,8 @@
 
 
 /*
- * 设置背景颜色
- * color -- 背景色
+ * 璁剧疆鑳屾櫙棰滆壊
+ * color -- 鑳屾櫙鑹�
  */
 void CEqsGraphWnd::SetBkgndColor(COLORREF color)
 {
@@ -2363,8 +2386,8 @@
 }
 
 /*
- * 边框颜色
- * color -- 边框色
+ * 杈规棰滆壊
+ * color -- 杈规鑹�
  */
 void CEqsGraphWnd::SetFrameColor(COLORREF color)
 {
@@ -2408,11 +2431,15 @@
 	SetTimer(m_hWnd, TIMER_ANIMATION_RECT, uElpase, NULL);
 }
 
-void CEqsGraphWnd::ShowItemIndicator(DWORD_PTR dwItemData, BOOL bShow)
+void CEqsGraphWnd::ShowItemIndicator(DWORD_PTR dwItemData, int state, int nIndex)
 {
+	if (nIndex < 0 || nIndex >= EQITEM_INDICATOR_COUNT) {
+		return;
+	}
+
 	EQITEM* pItem = GetItem(dwItemData);
 	if (pItem != nullptr) {
-		pItem->bShowIndicator[0] = bShow;
+		pItem->nIndicatorState[nIndex] = (BYTE)state;
 		::InvalidateRect(m_hWnd, nullptr, TRUE);
 	}
 }
diff --git a/SourceCode/Bond/Servo/EqsGraphWnd.h b/SourceCode/Bond/Servo/EqsGraphWnd.h
index c54d768..aaf9804 100644
--- a/SourceCode/Bond/Servo/EqsGraphWnd.h
+++ b/SourceCode/Bond/Servo/EqsGraphWnd.h
@@ -1,4 +1,4 @@
-#pragma once
+锘�#pragma once
 #include <functional>
 
 
@@ -35,6 +35,16 @@
 #define MAX(X,Y) (((X)>(Y))?(X):(Y))
 #endif
 
+#define EQITEM_INDICATOR_COUNT		8
+
+
+enum EIndicatorState
+{
+	INDICATOR_STATE_HIDDEN = 0,
+	INDICATOR_STATE_HIGHLIGHT = 1,
+	INDICATOR_STATE_GRAY = 2,
+};
+
 typedef struct tagEQSGRAPHWND_NMHDR
 {
 	NMHDR		nmhdr;
@@ -54,7 +64,7 @@
 	DWORD_PTR pInPins;
 	DWORD_PTR pOutPins;
 	int nFlashFlag;
-	BOOL bShowIndicator[2];
+	BYTE nIndicatorState[EQITEM_INDICATOR_COUNT]; // 0=闅愯棌, 1=楂樹寒, 2=鐏拌壊
 } EQITEM;
 
 typedef struct tagPIN
@@ -144,7 +154,9 @@
 	void SetItemPos(EQITEM* pItem, int x, int y);
 	void FlashItem(EQITEM* pItem);
 	void AnimationItem(EQITEM*pItem);
-	void ShowItemIndicator(DWORD_PTR dwItemData, BOOL bShow);
+	void ShowItemIndicator(DWORD_PTR dwItemData, int state, int nIndex = 0);
+	void SetIndicatorSize(int nSize);
+	void SetIndicatorMargin(int nMargin);
 
 private:
 	void Init();
@@ -192,7 +204,7 @@
 	EQITEM*		m_pFlashItem;
 	EQITEM*		m_pAnimationItem;
 	PIN *		m_pCurPin;
-	PIN *		m_pSelLineOutPin;		// 选中的连线的两个pin中的out pin
+	PIN *		m_pSelLineOutPin;		// 閫変腑鐨勮繛绾跨殑涓や釜pin涓殑out pin
 
 private:
 	HWND		m_hWnd;
@@ -201,12 +213,12 @@
 	HFONT		m_hFontTitle;
 
 private:
-	BOOL m_bUseGdiPlus;					// 使用GDI+绘图?
-	COLORREF m_crItemBackground[2];		// item的颜色,normal, active
-	COLORREF m_crItemFrame[2];			// item的边框,normal, active
+	BOOL m_bUseGdiPlus;					// 浣跨敤GDI+缁樺浘锛�
+	COLORREF m_crItemBackground[2];		// item鐨勯鑹诧紝normal锛� active
+	COLORREF m_crItemFrame[2];			// item鐨勮竟妗嗭紝normal锛� active
 	COLORREF m_crItemNameText[2];
 	COLORREF m_crItemIdText[2];
-	COLORREF m_crPinBkgnd[3];			// pin的颜色,normal, active, enable connect
+	COLORREF m_crPinBkgnd[3];			// pin鐨勯鑹诧紝normal, active, enable connect
 	int m_nCurSel;
 	EqsGraphListener m_listener;
 	CPtrArray m_arItem;
@@ -214,18 +226,20 @@
 	int m_nItemRound;
 
 private:
-	int m_nStageCx;			// 画布大小
+	int m_nStageCx;			// 鐢诲竷澶у皬
 	int m_nStageCy;
 	int m_nOffsetX;
 	int m_nOffsetY;
+	int m_nIndicatorSize;
+	int m_nIndicatorMargin;
 
-	// 动画
+	// 鍔ㄧ敾
 	RECTF m_rcAnimation;
 	RECTF m_rcAninationStep;
 	int m_nAninationStep;
 	int m_nAninationDuration;		// ms
 
-	// 字体
+	// 瀛椾綋
 	HFONT m_hFontName;
 	HFONT m_hFontId;
 
diff --git a/SourceCode/Bond/Servo/HmLabel.cpp b/SourceCode/Bond/Servo/HmLabel.cpp
new file mode 100644
index 0000000..c60cacb
--- /dev/null
+++ b/SourceCode/Bond/Servo/HmLabel.cpp
@@ -0,0 +1,177 @@
+#include "stdafx.h"
+#include "HmLabel.h"
+
+
+CHmLabel::CHmLabel()
+{
+	m_crFrame = RGB(128, 128, 128);
+	m_crBackground = RGB(255, 0, 0);
+	m_crForeground = RGB(255, 255, 255);
+	m_hFont = NULL;
+	m_hFontNote = NULL;
+}
+
+
+CHmLabel::~CHmLabel()
+{
+	if (m_hFont != NULL) {
+		::DeleteObject(m_hFont);
+		m_hFont = NULL;
+	}
+
+	if (m_hFontNote != NULL) {
+		::DeleteObject(m_hFontNote);
+		m_hFontNote = NULL;
+	}
+}
+
+BEGIN_MESSAGE_MAP(CHmLabel, CStatic)
+	ON_WM_PAINT()
+	ON_WM_NCPAINT()
+END_MESSAGE_MAP()
+
+void CHmLabel::setText(CString strText)
+{
+	SetWindowText(strText);
+	Invalidate();
+}
+
+void CHmLabel::setNote1(CString strNote1)
+{
+	m_strNote1 = strNote1;
+}
+
+void CHmLabel::setFontSize(int size)
+{
+	if (m_hFont != NULL) {
+		::DeleteObject(m_hFont);
+		m_hFont = NULL;
+	}
+
+	m_hFont = CreateFont(size, 0, 0, 0, FW_MEDIUM,
+		FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_CHARACTER_PRECIS,
+		CLIP_CHARACTER_PRECIS, DEFAULT_QUALITY, FF_DONTCARE, _T("宋体"));
+}
+
+void CHmLabel::setNoteFontSize(int size)
+{
+	if (m_hFontNote != NULL) {
+		::DeleteObject(m_hFontNote);
+		m_hFontNote = NULL;
+	}
+
+	m_hFontNote = CreateFont(size, 0, 0, 0, FW_MEDIUM,
+		FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_CHARACTER_PRECIS,
+		CLIP_CHARACTER_PRECIS, DEFAULT_QUALITY, FF_DONTCARE, _T("宋体"));
+}
+
+void CHmLabel::setNoteTextColor(COLORREF color, BOOL bInvalidate/* = FALSE*/)
+{
+	m_crNote = color;
+	if (bInvalidate) Invalidate(TRUE);
+}
+
+void CHmLabel::setBackground(COLORREF color, BOOL bInvalidate/* = FALSE*/)
+{
+	m_crBackground = color;
+	if (bInvalidate) Invalidate(TRUE);
+}
+
+void CHmLabel::setForeground(COLORREF color, BOOL bInvalidate/* = FALSE*/)
+{
+	m_crForeground = color;
+	if (bInvalidate) Invalidate(TRUE);
+}
+
+void CHmLabel::OnPaint()
+{
+	CPaintDC dc(this); // device context for painting
+					   // TODO: 在此处添加消息处理程序代码
+					   // 不为绘图消息调用 CStatic::OnPaint()
+
+
+	HDC hMemDC;
+	HBITMAP hBitmap;
+	RECT rcClient;
+	CString strText;
+	HBRUSH hBrushBK;
+
+
+	GetClientRect(&rcClient);
+	hMemDC = ::CreateCompatibleDC(dc.m_hDC);
+	hBitmap = ::CreateCompatibleBitmap(dc.m_hDC, rcClient.right - rcClient.left,
+		rcClient.bottom - rcClient.top);
+	::SelectObject(hMemDC, hBitmap);
+
+	
+	// 背景颜色
+	hBrushBK = CreateSolidBrush(m_crBackground);
+	::FillRect(hMemDC, &rcClient, hBrushBK);
+	DeleteObject(hBrushBK);
+
+
+	// 文字
+	char szText[256];
+	GetWindowText(szText, 256);
+	RECT rcText = rcClient;
+	SetBkMode(hMemDC, TRANSPARENT);
+	SetTextColor(hMemDC, m_crForeground);
+	::SelectObject(hMemDC, m_hFont == NULL ? (HFONT)GetStockObject(DEFAULT_GUI_FONT) : m_hFont);
+	::DrawTextA(hMemDC, szText, (int)strlen(szText), &rcText, DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
+
+
+	// Note1
+	SetTextColor(hMemDC, m_crNote);
+	::SelectObject(hMemDC, m_hFontNote == NULL ? (HFONT)GetStockObject(DEFAULT_GUI_FONT) : m_hFontNote);
+	::DrawText(hMemDC, m_strNote1, m_strNote1.GetLength(), &rcClient, DT_CENTER | DT_BOTTOM | DT_SINGLELINE | DT_END_ELLIPSIS);
+
+
+	// EndPaint
+	::BitBlt(dc.m_hDC, 0, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top,
+		hMemDC, 0, 0, SRCCOPY);
+	::DeleteObject(hBitmap);
+	::DeleteDC(hMemDC);
+}
+
+
+void CHmLabel::OnNcPaint()
+{
+	// TODO: 在此处添加消息处理程序代码
+	// 不为绘图消息调用 CStatic::OnNcPaint()
+	long styleEx = GetWindowLong(m_hWnd, GWL_EXSTYLE);
+	if ((styleEx & WS_EX_CLIENTEDGE) == WS_EX_CLIENTEDGE) {
+
+		RECT rect, rcClient;
+		GetClientRect(&rcClient);
+		::ClientToScreen(m_hWnd, (LPPOINT)&rcClient.left);
+		::ClientToScreen(m_hWnd, (LPPOINT)&rcClient.right);
+		GetWindowRect(&rect);
+		::OffsetRect(&rcClient, -rect.left, -rect.top);
+
+		rect.right -= rect.left;
+		rect.bottom -= rect.top;
+		rect.left = 0;
+		rect.top = 0;
+
+		HRGN hRgnWnd = CreateRectRgnIndirect(&rect);
+		HRGN hRgnClient = CreateRectRgnIndirect(&rcClient);
+
+		HBRUSH hBrushBK, hBrushFrame;
+		HDC hDC = ::GetWindowDC(m_hWnd);
+		::SelectClipRgn(hDC, hRgnWnd);
+		::ExtSelectClipRgn(hDC, hRgnClient, RGN_DIFF);
+
+		hBrushBK = CreateSolidBrush(m_crBackground);
+		::FillRect(hDC, &rect, hBrushBK);
+		DeleteObject(hBrushBK);
+
+		hBrushFrame = CreateSolidBrush(m_crFrame);
+		::FrameRect(hDC, &rect, hBrushFrame);
+
+		::DeleteObject(hRgnWnd);
+		::DeleteObject(hRgnClient);
+		DeleteObject(hBrushFrame);
+		::ReleaseDC(m_hWnd, hDC);
+	}
+}
+
diff --git a/SourceCode/Bond/Servo/HmLabel.h b/SourceCode/Bond/Servo/HmLabel.h
new file mode 100644
index 0000000..d7623e1
--- /dev/null
+++ b/SourceCode/Bond/Servo/HmLabel.h
@@ -0,0 +1,33 @@
+#pragma once
+class CHmLabel : public CStatic
+{
+public:
+	CHmLabel();
+	~CHmLabel();
+
+public:
+	void setText(CString strText);
+	void setNote1(CString strNote1);
+	void setFontSize(int size);
+	void setNoteFontSize(int size);
+	void setBackground(COLORREF color, BOOL bInvalidate = FALSE);
+	void setForeground(COLORREF color, BOOL bInvalidate = FALSE);
+	void setNoteTextColor(COLORREF color, BOOL bInvalidate = FALSE);
+
+private:
+	COLORREF m_crFrame;
+	COLORREF m_crBackground;
+	COLORREF m_crForeground;
+	COLORREF m_crNote;
+	HFONT m_hFont;
+	HFONT m_hFontNote;
+
+private:
+	CString m_strNote1;
+
+public:
+	DECLARE_MESSAGE_MAP()
+	afx_msg void OnPaint();
+	afx_msg void OnNcPaint();
+};
+
diff --git a/SourceCode/Bond/Servo/HsmsAction.cpp b/SourceCode/Bond/Servo/HsmsAction.cpp
index 1c0d71c..1257696 100644
--- a/SourceCode/Bond/Servo/HsmsAction.cpp
+++ b/SourceCode/Bond/Servo/HsmsAction.cpp
@@ -9,6 +9,7 @@
 	m_nTimeout = 45;
 	m_nResponseTime = 0;
 	m_pContext = NULL;
+	m_pSendMessage = NULL;
 	m_hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
 }
 
@@ -19,6 +20,7 @@
 	m_nTimeout = nTimeout;
 	m_nResponseTime = 0;
 	m_pContext = NULL;
+	m_pSendMessage = NULL;
 	m_hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
 }
 
@@ -41,6 +43,10 @@
 	m_bNeedWaitReply = FALSE;
 	m_nTimeout = 45;
 	m_nResponseTime = 0;
+	if (m_pSendMessage != NULL) {
+		HSMS_Destroy1Message(m_pSendMessage);
+		m_pSendMessage = NULL;
+	}
 	::ResetEvent(m_hEvent);
 }
 
@@ -73,6 +79,9 @@
 
 void CHsmsAction::setSendMessage(IMessage* pMessage)
 {
+	if (m_pSendMessage != NULL && m_pSendMessage != pMessage) {
+		HSMS_Destroy1Message(m_pSendMessage);
+	}
 	m_pSendMessage = pMessage;
 }
 
@@ -110,12 +119,15 @@
 int CHsmsAction::serialize(char* pszBuffer, int nBufferSize)
 {
 	int index = 0;
+	if (m_pSendMessage == NULL) {
+		return 0;
+	}
 	if (pszBuffer == nullptr) {
 		index += sizeof(int);
 		index += sizeof(m_nTimeout);
 		index += sizeof(int);
 		index += sizeof(BOOL);
-		index += m_pSendMessage->serialize(pszBuffer, nBufferSize);
+		index += m_pSendMessage->serialize(nullptr, 0);
 
 		return index;
 	}
@@ -157,7 +169,13 @@
 	memcpy(&m_bNeedWaitReply, &pszBuffer[index], sizeof(BOOL));
 	index += sizeof(BOOL);
 
-	HSMS_Create1Message(m_pSendMessage, 1, 1 | REPLY, 1, 1);
+	if (m_pSendMessage != NULL) {
+		HSMS_Destroy1Message(m_pSendMessage);
+		m_pSendMessage = NULL;
+	}
+	if (HSMS_Create1Message(m_pSendMessage, 1, 1 | REPLY, 1, 1) != 0 || m_pSendMessage == NULL) {
+		return -1;
+	}
 	int nRet = m_pSendMessage->unserialize(&pszBuffer[index], nBufferSize - index);
 	if (nRet < 0) return nRet;
 
diff --git a/SourceCode/Bond/Servo/HsmsPassive.cpp b/SourceCode/Bond/Servo/HsmsPassive.cpp
index 1b122ec..98d6a85 100644
--- a/SourceCode/Bond/Servo/HsmsPassive.cpp
+++ b/SourceCode/Bond/Servo/HsmsPassive.cpp
@@ -3,17 +3,78 @@
 #include "Log.h"
 #include "Model.h"
 #include "Common.h"
+#include "RecipeManager.h"
 #include <time.h>
 #include <iostream>  
 #include <time.h>  
 #include <stdlib.h>  
 #include <string.h>  
+#include <algorithm>
+#include <set>
 #include <regex>
+#include <sstream>
+
+// ---- Encoding helpers ----
+static bool hasUtf8Bom(const std::string& s)
+{
+	return s.size() >= 3 &&
+		static_cast<unsigned char>(s[0]) == 0xEF &&
+		static_cast<unsigned char>(s[1]) == 0xBB &&
+		static_cast<unsigned char>(s[2]) == 0xBF;
+}
+
+static bool isLikelyUtf8(const std::string& s)
+{
+	// Simple heuristic: try to convert; if success without errors, treat as UTF-8.
+	int wlen = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s.c_str(), (int)s.size(), nullptr, 0);
+	return wlen > 0;
+}
+
+static CStringW Utf8ToWide(const char* psz)
+{
+	if (psz == nullptr) return L"";
+	int wlen = MultiByteToWideChar(CP_UTF8, 0, psz, -1, nullptr, 0);
+	if (wlen <= 0) return L"";
+	CStringW ws;
+	LPWSTR buf = ws.GetBufferSetLength(wlen - 1);
+	MultiByteToWideChar(CP_UTF8, 0, psz, -1, buf, wlen);
+	ws.ReleaseBuffer();
+	return ws;
+}
+
+static CStringW AnsiToWide(const char* psz)
+{
+	if (psz == nullptr) return L"";
+	int wlen = MultiByteToWideChar(CP_ACP, 0, psz, -1, nullptr, 0);
+	if (wlen <= 0) return L"";
+	CStringW ws;
+	LPWSTR buf = ws.GetBufferSetLength(wlen - 1);
+	MultiByteToWideChar(CP_ACP, 0, psz, -1, buf, wlen);
+	ws.ReleaseBuffer();
+	return ws;
+}
+// ---- End helpers ----
+
+// ControlState values (keep in sync with Model::ControlState / VariableList.txt)
+static constexpr uint8_t kControlStateOnlineRemote = 5;
 
 
 const char ACK[2] = {0, 1};
 const char* ACK0 = &ACK[0];
 const char* ACK1 = &ACK[1];
+
+// Log SECS-II message briefly to avoid huge strings causing issues.
+static void LogSecsMessageBrief(const char* tag, IMessage* pMessage, size_t maxLen = 1024)
+{
+	if (pMessage == nullptr) return;
+	const char* msgStr = pMessage->toString();
+	if (msgStr == nullptr) return;
+	std::string buf(msgStr);
+	if (buf.size() > maxLen) {
+		buf = buf.substr(0, maxLen) + "...<truncated>";
+	}
+	LOGI("%s%s", tag, buf.c_str());
+}
 
 unsigned __stdcall CimWorkThreadFunction(LPVOID lpParam)
 {
@@ -46,8 +107,6 @@
 	m_listener.onEQOffLine = nullptr;
 	m_listener.onEQOnLine = nullptr;
 	m_listener.onCommand = nullptr;
-	m_listener.onEQConstantRequest = nullptr;
-	m_listener.onEQConstantSend = nullptr;
 	m_pActiveAction = nullptr;
 	InitializeCriticalSection(&m_criticalSection);
 }
@@ -108,23 +167,35 @@
 	ASSERT(pParent);
 	ASSERT(pVariable);
 
+	std::string svNote("SV");
+	{
+		SERVO::CVariable* pDef = getVariable((int)pVariable->getVarialbleId());
+		if (pDef == nullptr) {
+			pDef = pVariable;
+		}
+		auto& name = pDef->getName();
+		if (!name.empty()) {
+			svNote += " -> ";
+			svNote += name;
+		}
+	}
 
 	ISECS2Item* pItemList;
 	SERVO::SVFromat format = pVariable->getFormat();
 	switch (format)
 	{
 	case SERVO::SVFromat::U1:
-		pParent->addU1Item((unsigned char)pVariable->getIntValue(), "SV");
+		pParent->addU1Item((unsigned char)pVariable->getIntValue(), svNote.c_str());
 		break;
 	case SERVO::SVFromat::U2:
-		pParent->addU2Item((unsigned char)pVariable->getIntValue(), "SV");
+		pParent->addU2Item((unsigned char)pVariable->getIntValue(), svNote.c_str());
 		break;
 	case SERVO::SVFromat::I2:
-		pParent->addI2Item((unsigned char)pVariable->getIntValue(), "SV");
+		pParent->addI2Item((unsigned char)pVariable->getIntValue(), svNote.c_str());
 		break;
 	case SERVO::SVFromat::A20:
 	case SERVO::SVFromat::A50:
-		pParent->addItem(pVariable->getValue().c_str(), "SV");
+		pParent->addItem(pVariable->getValue().c_str(), svNote.c_str());
 		break;
 	case SERVO::SVFromat::L:
 		pItemList = pParent->addItem();
@@ -151,23 +222,50 @@
 
 void CHsmsPassive::unlinkEventReport(unsigned int CEID)
 {
+	LOGI("<CHsmsPassive>unlinkEventReport enter");
 	SERVO::CCollectionEvent* pEvent = getEvent(CEID);
 	if (pEvent != nullptr) {
 		pEvent->setReport(nullptr);
+		LOGI("<CHsmsPassive>unlink Event Report.CEID=%d", CEID);
 	}
+}
+
+bool CHsmsPassive::shouldSpool(uint8_t streamId, uint8_t functionId) const
+{
+	// Comment: stream 1 is not spooled
+	if (streamId == 1) return false;
+
+	// Comment: m=0 turns off all streams and fns
+	if (!m_spoolingEnabled) return false;
+
+	// Blacklist semantics: in map => do NOT spool/cache.
+	// Not in map => allow spooling by default.
+	auto it = m_spoolBlacklistByStream.find(streamId);
+	if (it == m_spoolBlacklistByStream.end()) return true;
+
+	// Empty set => all functions in this stream
+	if (it->second.empty()) return true;
+
+	return it->second.find(functionId) == it->second.end();
 }
 
 SERVO::CReport* CHsmsPassive::defineReport(unsigned int RPTID, std::vector<unsigned int>& vids)
 {
+	LOGI("<CHsmsPassive>defineReport enter");
 	// 娣诲姞瀹氫箟report
 	SERVO::CReport* pReport = new SERVO::CReport(RPTID, vids);
 	for (auto vid : vids) {
 		SERVO::CVariable* pVariable = getVariable(vid);
+		if (pVariable == nullptr) {
+			pVariable = getDataVariable(vid);
+		}
 		if (pVariable != nullptr) {
 			pReport->addVariable(pVariable);
+			LOGI("<CHsmsPassive>defineReport RPTID=%d", RPTID);
 		}
 	}
 	m_reports.push_back(pReport);
+	writeReportsToFile(m_strReportFilepath);
 
 	return pReport;
 }
@@ -191,7 +289,7 @@
 
 int CHsmsPassive::onRecvMsg(IMessage* pMessage)
 {
-	LOGI("onRecvMsg:%s", pMessage->toString());
+	// LOGI("onRecvMsg:%s", pMessage->toString());
 	Lock();
 	if (m_pActiveAction != nullptr &&
 		(m_pActiveAction->getSendMessage()->getHeader()->systemBytes == pMessage->getHeader()->systemBytes)) {
@@ -228,40 +326,133 @@
 
 int CHsmsPassive::loadVarialbles(const char* pszFilepath)
 {
-	CStdioFile file;
-	if (!file.Open(pszFilepath, CFile::modeRead)) {
+	m_strVariableFilepath = pszFilepath;
+	m_bVariableUtf8 = false;
+	m_bVariableUtf8Bom = false;
+	// 鍏堣鍘熷瀛楄妭锛屽悗缁啀鎸� UTF-8/BOM 鎴栨湰鍦扮紪鐮佽浆鎹�
+	CFile file;
+	if (!file.Open(pszFilepath, CFile::modeRead | CFile::shareDenyNone)) {
 		return -1;
 	}
 
-	std::regex pattern("^\\d+,.*");  // 鍖归厤浠ユ暟瀛�+閫楀彿寮�澶寸殑瀛楃涓�
+	const ULONGLONG nLen = file.GetLength();
+	if (nLen == 0) {
+		return -1;
+	}
+
+	std::string buffer;
+	buffer.resize(static_cast<size_t>(nLen));
+	file.Read(buffer.data(), static_cast<UINT>(nLen));
+	file.Close();
+
+	const unsigned char* bytes = reinterpret_cast<const unsigned char*>(buffer.data());
+	size_t offset = 0;
+	CStringW content;
+
+	// UTF-8 BOM
+	if (nLen >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) {
+		offset = 3;
+		m_bVariableUtf8 = true;
+		m_bVariableUtf8Bom = true;
+	}
+
+	// UTF-16 LE BOM
+	if (nLen >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE) {
+		const wchar_t* wdata = reinterpret_cast<const wchar_t*>(buffer.data() + 2);
+		const size_t wlen = (nLen - 2) / sizeof(wchar_t);
+		content.SetString(wdata, static_cast<int>(wlen));
+	}
+	// UTF-16 BE BOM
+	else if (nLen >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF) {
+		const size_t wlen = (nLen - 2) / sizeof(wchar_t);
+		std::wstring temp;
+		temp.reserve(wlen);
+		for (size_t i = 0; i < wlen; ++i) {
+			wchar_t ch = static_cast<wchar_t>(bytes[2 + i * 2] << 8 | bytes[2 + i * 2 + 1]);
+			temp.push_back(ch);
+		}
+		content = temp.c_str();
+	}
+	// 灏濊瘯 UTF-8锛堝惈鏃� BOM锛�
+	else {
+		auto tryUtf8 = [&](size_t off) -> bool {
+			int need = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, buffer.data() + off,
+				static_cast<int>(buffer.size() - off), nullptr, 0);
+			if (need <= 0) return false;
+			std::wstring temp;
+			temp.resize(need);
+			MultiByteToWideChar(CP_UTF8, 0, buffer.data() + off,
+				static_cast<int>(buffer.size() - off), temp.data(), need);
+			content = temp.c_str();
+			m_bVariableUtf8 = true;
+			return true;
+		};
+
+		if (!tryUtf8(offset)) {
+			// 鍥為��鍒版湰鍦颁唬鐮侀〉
+			int need = MultiByteToWideChar(CP_ACP, 0, buffer.data(), static_cast<int>(buffer.size()), nullptr, 0);
+			if (need > 0) {
+				std::wstring temp;
+				temp.resize(need);
+				MultiByteToWideChar(CP_ACP, 0, buffer.data(), static_cast<int>(buffer.size()), temp.data(), need);
+				content = temp.c_str();
+			}
+		}
+	}
+
+	if (content.IsEmpty()) {
+		return -1;
+	}
+
+	std::wregex pattern(L"^\\d+,.+");  // 鍖归厤浠ユ暟瀛�+閫楀彿寮�澶寸殑瀛楃涓�
 	std::vector<SERVO::CVariable*> variables;
 	int index, last;
-	CString strLine;
-	CString strId, strName, strFormat, strRemark;
-	while (file.ReadString(strLine)) {
-		if (!std::regex_match((LPTSTR)(LPCTSTR)strLine, pattern)) {
+	CStringW strLine;
+	CStringW strId, strName, strFormat, strRemark;
+	std::wstringstream ss(content.GetString());
+	auto narrowFromW = [](const CStringW& s) -> std::string {
+		int need = WideCharToMultiByte(CP_ACP, 0, s, -1, nullptr, 0, nullptr, nullptr);
+		if (need <= 0) return {};
+		std::string out(static_cast<size_t>(need - 1), '\0');
+		WideCharToMultiByte(CP_ACP, 0, s, -1, out.data(), need, nullptr, nullptr);
+		return out;
+	};
+	std::wstring line;
+	while (std::getline(ss, line, L'\n')) {
+		strLine = line.c_str();
+		strLine.Trim();
+		if (strLine.IsEmpty()) continue;
+		if (!std::regex_match(static_cast<LPCWSTR>(strLine), pattern)) {
 			continue;
 		}
 
 		last = 0;
-		index = strLine.Find(",", last);
+		index = strLine.Find(L",", last);
 		if (index < 0) continue;
 		strId = strLine.Left(index);
 		last = index + 1;
 
-		index = strLine.Find(",", last);
+		index = strLine.Find(L",", last);
 		if (index < 0) continue;
 		strName = strLine.Mid(last, index - last);
 		last = index + 1;
 
-		index = strLine.Find(",", last);
+		index = strLine.Find(L",", last);
 		if (index < 0) continue;
 		strFormat = strLine.Mid(last, index - last);
 		strRemark = strLine.Right(strLine.GetLength() - index - 1);
-		strRemark.Replace(_T("\\r\\n"), _T("\r\n"));
+		strRemark.Replace(L"\\r\\n", L"\r\n");
+
+		std::string sId = narrowFromW(strId);
+		std::string sName = narrowFromW(strName);
+		std::string sFormat = narrowFromW(strFormat);
+		std::string sRemark = narrowFromW(strRemark);
 
 		SERVO::CVariable* pVarialble = new SERVO::CVariable(
-			(LPTSTR)(LPCTSTR)strId, (LPTSTR)(LPCTSTR)strName, (LPTSTR)(LPCTSTR)strFormat, (LPTSTR)(LPCTSTR)strRemark);
+			sId.c_str(),
+			sName.c_str(),
+			sFormat.c_str(),
+			sRemark.c_str());
 		variables.push_back(pVarialble);
 	}
 
@@ -272,14 +463,230 @@
 		}
 	}
 
+	return 0;
+}
 
+int CHsmsPassive::loadDataVarialbles(const char* pszFilepath)
+{
+	if (pszFilepath == NULL) {
+		return -1;
+	}
+	m_strDataVariableFilepath = pszFilepath;
+	m_bDataVariableUtf8 = false;
+	m_bDataVariableUtf8Bom = false;
+	CFile file;
+	if (!file.Open(pszFilepath, CFile::modeRead | CFile::shareDenyNone)) {
+		return -1;
+	}
+	const ULONGLONG nLen = file.GetLength();
+	if (nLen == 0) {
+		return -1;
+	}
+	std::string buffer;
+	buffer.resize(static_cast<size_t>(nLen));
+	file.Read(buffer.data(), static_cast<UINT>(nLen));
 	file.Close();
+
+	if (hasUtf8Bom(buffer)) {
+		m_bDataVariableUtf8 = true;
+		m_bDataVariableUtf8Bom = true;
+		buffer = buffer.substr(3);
+	}
+	else if (isLikelyUtf8(buffer)) {
+		m_bDataVariableUtf8 = true;
+	}
+	CStringW content = m_bDataVariableUtf8 ? Utf8ToWide(buffer.c_str()) : AnsiToWide(buffer.c_str());
+
+	// Regex: DVID,DV Name,DV Format,DV Remark
+	std::wregex pattern(L"^\\d+,[^,]*,[^,]*,.*");
+	std::vector<SERVO::CDataVariable*> dataVars;
+	int index;
+	CStringW strLine, strId, strName, strFormat, strRemark;
+	std::wstringstream ss(content.GetString());
+	auto narrowFromW = [](const CStringW& s) -> std::string {
+		int need = WideCharToMultiByte(CP_ACP, 0, s, -1, nullptr, 0, nullptr, nullptr);
+		if (need <= 0) return {};
+		std::string out(static_cast<size_t>(need - 1), '\0');
+		WideCharToMultiByte(CP_ACP, 0, s, -1, out.data(), need, nullptr, nullptr);
+		return out;
+	};
+	std::wstring line;
+	while (std::getline(ss, line, L'\n')) {
+		strLine = line.c_str();
+		strLine.Trim();
+		if (strLine.IsEmpty()) continue;
+		if (!std::regex_match(static_cast<LPCWSTR>(strLine), pattern)) {
+			continue;
+		}
+		index = strLine.Find(L",", 0);
+		if (index < 0) continue;
+		strId = strLine.Left(index);
+
+		strLine = strLine.Right(strLine.GetLength() - index - 1);
+		index = strLine.Find(L",", 0);
+		if (index < 0) continue;
+		strName = strLine.Left(index);
+
+		strLine = strLine.Right(strLine.GetLength() - index - 1);
+		index = strLine.Find(L",", 0);
+		if (index < 0) continue;
+		strFormat = strLine.Left(index);
+
+		strRemark = strLine.Right(strLine.GetLength() - index - 1);
+		strRemark.Replace(L"\\r\\n", L"\r\n");
+
+		std::string sId = narrowFromW(strId);
+		std::string sName = narrowFromW(strName);
+		std::string sFormat = narrowFromW(strFormat);
+		std::string sRemark = narrowFromW(strRemark);
+
+		SERVO::CDataVariable* pVarialble = new SERVO::CDataVariable(
+			sId.c_str(),
+			sName.c_str(),
+			sFormat.c_str(),
+			sRemark.c_str());
+		dataVars.push_back(pVarialble);
+	}
+
+	if (!dataVars.empty()) {
+		clearAllDataVariabel();
+		for (auto item : dataVars) {
+			m_dataVariabels.push_back(item);
+		}
+	}
+
+	return 0;
+}
+
+int CHsmsPassive::loadEquipmentConstants(const char* pszFilepath)
+{
+	if (pszFilepath == NULL) return -1;
+	m_strEquipmentConstantFilepath = pszFilepath;
+	m_bEquipmentConstantUtf8 = false;
+	m_bEquipmentConstantUtf8Bom = false;
+
+	CFile file;
+	if (!file.Open(pszFilepath, CFile::modeRead | CFile::shareDenyNone)) {
+		return -1;
+	}
+	const ULONGLONG nLen = file.GetLength();
+	if (nLen == 0) {
+		return -1;
+	}
+	std::string buffer;
+	buffer.resize(static_cast<size_t>(nLen));
+	file.Read(buffer.data(), static_cast<UINT>(nLen));
+	file.Close();
+
+	if (hasUtf8Bom(buffer)) {
+		m_bEquipmentConstantUtf8 = true;
+		m_bEquipmentConstantUtf8Bom = true;
+		buffer = buffer.substr(3);
+	}
+	else if (isLikelyUtf8(buffer)) {
+		m_bEquipmentConstantUtf8 = true;
+	}
+	CStringW content = m_bEquipmentConstantUtf8 ? Utf8ToWide(buffer.c_str()) : AnsiToWide(buffer.c_str());
+	if (content.IsEmpty()) return -1;
+
+	std::wregex pattern(L"^\\d+,[^,]*,[^,]*,([^,]*),.*");
+	std::vector<EquipmentConstantEntry> constants;
+	CStringW strLine, strId, strName, strFormat, strRemark, strDefault;
+	std::wstringstream ss(content.GetString());
+	auto narrowFromW = [](const CStringW& s) -> std::string {
+		int need = WideCharToMultiByte(CP_ACP, 0, s, -1, nullptr, 0, nullptr, nullptr);
+		if (need <= 0) return {};
+		std::string out(static_cast<size_t>(need - 1), '\0');
+		WideCharToMultiByte(CP_ACP, 0, s, -1, out.data(), need, nullptr, nullptr);
+		return out;
+	};
+	std::wstring line;
+	while (std::getline(ss, line, L'\n')) {
+		strLine = line.c_str();
+		strLine.Trim();
+		if (strLine.IsEmpty()) continue;
+		if (strLine.Find(L"ECID") == 0) continue; // skip header
+		if (!std::regex_match(static_cast<LPCWSTR>(strLine), pattern)) {
+			continue;
+		}
+		int last = 0;
+		int idx = strLine.Find(L",", last);
+		if (idx < 0) continue;
+		strId = strLine.Left(idx);
+		last = idx + 1;
+
+		idx = strLine.Find(L",", last);
+		if (idx < 0) continue;
+		strName = strLine.Mid(last, idx - last);
+		last = idx + 1;
+
+		idx = strLine.Find(L",", last);
+		if (idx < 0) continue;
+		strFormat = strLine.Mid(last, idx - last);
+		last = idx + 1;
+
+		idx = strLine.Find(L",", last);
+		if (idx < 0) continue;
+		strRemark = strLine.Mid(last, idx - last);
+		last = idx + 1;
+
+		strDefault = strLine.Right(strLine.GetLength() - last);
+
+		EquipmentConstantEntry entry;
+		entry.id = _wtoi(strId);
+		entry.name = narrowFromW(strName);
+		entry.format = narrowFromW(strFormat);
+		entry.remark = narrowFromW(strRemark);
+		entry.value = narrowFromW(strDefault);
+		constants.push_back(entry);
+	}
+
+	if (!constants.empty()) {
+		m_equipmentConstants = std::move(constants);
+	}
 	return 0;
 }
 
 std::vector<SERVO::CVariable*>& CHsmsPassive::getVariables()
 {
 	return m_variabels;
+}
+
+std::vector<SERVO::CDataVariable*>& CHsmsPassive::getDataVariables()
+{
+	return m_dataVariabels;
+}
+
+unsigned int CHsmsPassive::getMaxVariableId() const
+{
+	unsigned int maxId = 0;
+	for (auto item : m_variabels) {
+		if (item && item->getVarialbleId() > maxId) {
+			maxId = item->getVarialbleId();
+		}
+	}
+	for (auto item : m_dataVariabels) {
+		if (item && item->getVarialbleId() > maxId) {
+			maxId = item->getVarialbleId();
+		}
+	}
+	return maxId;
+}
+
+unsigned int CHsmsPassive::getMaxDataVariableId() const
+{
+	unsigned int maxId = 0;
+	for (auto item : m_variabels) {
+		if (item && item->getVarialbleId() > maxId) {
+			maxId = item->getVarialbleId();
+		}
+	}
+	for (auto item : m_dataVariabels) {
+		if (item && item->getVarialbleId() > maxId) {
+			maxId = item->getVarialbleId();
+		}
+	}
+	return maxId;
 }
 
 SERVO::CVariable* CHsmsPassive::getVariable(int variableId)
@@ -300,8 +707,46 @@
 			return item;
 		}
 	}
+	// try numeric id string
+	if (pszName != nullptr && *pszName) {
+		const int id = atoi(pszName);
+		if (id > 0) {
+			return getVariable(id);
+		}
+	}
 
 	return nullptr;
+}
+
+SERVO::CDataVariable* CHsmsPassive::getDataVariable(int dvid)
+{
+	for (auto item : m_dataVariabels) {
+		if (item->getVarialbleId() == (unsigned int)dvid) return item;
+	}
+	return nullptr;
+}
+
+SERVO::CDataVariable* CHsmsPassive::getDataVariable(const char* pszName)
+{
+	for (auto item : m_dataVariabels) {
+		if (item->getName().compare(pszName) == 0) return item;
+	}
+	return nullptr;
+}
+
+int CHsmsPassive::getCurrentControlState()
+{
+	auto v = getVariable("CurrentControlState");
+	if (v != nullptr) {
+		return static_cast<int>(v->getIntValue());
+	}
+	return 0;
+}
+
+bool CHsmsPassive::isHostCommandAllowed()
+{
+	// Only allow host control commands in OnlineRemote.
+	return getCurrentControlState() == kControlStateOnlineRemote;
 }
 
 void CHsmsPassive::clearAllVariabel()
@@ -312,58 +757,447 @@
 	m_variabels.clear();
 }
 
+void CHsmsPassive::clearAllDataVariabel()
+{
+	for (auto item : m_dataVariabels) {
+		delete item;
+	}
+	m_dataVariabels.clear();
+}
+
+CStringA WideToUtf8(const CStringW& ws)
+{
+	int need = WideCharToMultiByte(CP_UTF8, 0, ws, -1, nullptr, 0, nullptr, nullptr);
+	if (need <= 0) return "";
+	CStringA out;
+	LPSTR buf = out.GetBufferSetLength(need - 1);
+	WideCharToMultiByte(CP_UTF8, 0, ws, -1, buf, need, nullptr, nullptr);
+	out.ReleaseBuffer();
+	return out;
+}
+
+CStringA WideToAnsi(const CStringW& ws)
+{
+	int need = WideCharToMultiByte(CP_ACP, 0, ws, -1, nullptr, 0, nullptr, nullptr);
+	if (need <= 0) return "";
+	CStringA out;
+	LPSTR buf = out.GetBufferSetLength(need - 1);
+	WideCharToMultiByte(CP_ACP, 0, ws, -1, buf, need, nullptr, nullptr);
+	out.ReleaseBuffer();
+	return out;
+}
+
+static CStringA AnsiToUtf8(const std::string& s)
+{
+	int wlen = MultiByteToWideChar(CP_ACP, 0, s.c_str(), -1, nullptr, 0);
+	if (wlen <= 0) return "";
+	CStringW ws;
+	LPWSTR wbuf = ws.GetBufferSetLength(wlen - 1);
+	MultiByteToWideChar(CP_ACP, 0, s.c_str(), -1, wbuf, wlen);
+	ws.ReleaseBuffer();
+	return WideToUtf8(ws);
+}
+
+int CHsmsPassive::deleteVariable(int variableId)
+{
+	Lock();
+	auto it = std::find_if(m_variabels.begin(), m_variabels.end(), [=](SERVO::CVariable* v) {
+		return v != nullptr && v->getVarialbleId() == variableId;
+		});
+	if (it == m_variabels.end()) {
+		Unlock();
+		return -1;
+	}
+	delete *it;
+	m_variabels.erase(it);
+	auto filepath = m_strVariableFilepath;
+	Unlock();
+
+	if (filepath.empty()) return -2;
+
+	return writeVariablesToFile(filepath);
+}
+
 void CHsmsPassive::setVariableValue(const char* pszName, __int64 value)
 {
-	auto v = getVariable(pszName);
-	if (v != nullptr) {
+	// Protect variable list updates; multiple threads may set SVs.
+	Lock();
+	if (auto v = getVariable(pszName)) {
 		v->setValue(value);
 	}
+	else if (auto dv = getDataVariable(pszName)) {
+		dv->setValue(value);
+	}
+	Unlock();
 }
 
 void CHsmsPassive::setVariableValue(const char* pszName, const char* value)
 {
-	auto v = getVariable(pszName);
-	if (v != nullptr) {
+	Lock();
+	if (auto v = getVariable(pszName)) {
 		v->setValue(value);
 	}
+	else if (auto dv = getDataVariable(pszName)) {
+		dv->setValue(value);
+	}
+	Unlock();
 }
 
 void CHsmsPassive::setVariableValue(const char* pszName, std::vector<SERVO::CVariable>& vars)
 {
-	auto v = getVariable(pszName);
-	if (v != nullptr) {
+	Lock();
+	if (auto v = getVariable(pszName)) {
 		v->setValue(vars);
 	}
+	else if (auto dv = getDataVariable(pszName)) {
+		dv->setValue(vars);
+	}
+	Unlock();
+}
+
+void CHsmsPassive::withVariableLock(const std::function<void()>& fn)
+{
+	Lock();
+	if (fn) fn();
+	Unlock();
+}
+
+static bool isValidFormat(const std::string& fmt)
+{
+	static const std::set<std::string> allow = { "U1","U2","I2","A20","A50","L" };
+	return allow.count(fmt) > 0;
+}
+
+int CHsmsPassive::addVariable(const char* pszName, const char* pszFormat, const char* pszRemark, int& outId)
+{
+	if (pszName == nullptr || pszFormat == nullptr) return -1;
+	std::string fmt = pszFormat;
+	std::transform(fmt.begin(), fmt.end(), fmt.begin(), ::toupper);
+	if (!isValidFormat(fmt)) return -2;
+
+	Lock();
+	int maxId = 0;
+	for (auto v : m_variabels) {
+		if (v != nullptr && static_cast<int>(v->getVarialbleId()) > maxId) {
+			maxId = static_cast<int>(v->getVarialbleId());
+		}
+	}
+	outId = maxId + 1;
+
+	SERVO::CVariable* pNew = new SERVO::CVariable(std::to_string(outId).c_str(), pszName, fmt.c_str(), pszRemark ? pszRemark : "");
+	m_variabels.push_back(pNew);
+	auto filepath = m_strVariableFilepath;
+	Unlock();
+
+	if (filepath.empty()) return -3;
+	return writeVariablesToFile(filepath);
+}
+
+int CHsmsPassive::updateVariable(int variableId, const char* pszName, const char* pszFormat, const char* pszRemark)
+{
+	if (pszName == nullptr || pszFormat == nullptr) return -1;
+	std::string fmt = pszFormat;
+	std::transform(fmt.begin(), fmt.end(), fmt.begin(), ::toupper);
+	if (!isValidFormat(fmt)) return -2;
+
+	Lock();
+	auto it = std::find_if(m_variabels.begin(), m_variabels.end(), [=](SERVO::CVariable* v) {
+		return v != nullptr && v->getVarialbleId() == variableId;
+		});
+	if (it == m_variabels.end()) {
+		Unlock();
+		return -4;
+	}
+	(*it)->setName(pszName);
+	(*it)->setFormat(fmt.c_str());
+	(*it)->setRemark(pszRemark ? pszRemark : "");
+	auto filepath = m_strVariableFilepath;
+	Unlock();
+
+	if (filepath.empty()) return -3;
+	return writeVariablesToFile(filepath);
+}
+
+int CHsmsPassive::deleteDataVariable(int dvid)
+{
+	Lock();
+	auto it = std::find_if(m_dataVariabels.begin(), m_dataVariabels.end(), [=](SERVO::CDataVariable* v) {
+		return v != nullptr && v->getVarialbleId() == (unsigned int)dvid;
+		});
+	if (it == m_dataVariabels.end()) {
+		Unlock();
+		return -1;
+	}
+	delete *it;
+	m_dataVariabels.erase(it);
+	auto filepath = m_strDataVariableFilepath;
+	Unlock();
+
+	if (filepath.empty()) return -2;
+	return writeDataVariablesToFile(filepath);
+}
+
+int CHsmsPassive::addDataVariable(const char* pszName, const char* pszFormat, const char* pszRemark, int& outId)
+{
+	if (pszName == nullptr || pszFormat == nullptr) return -1;
+	std::string fmt = pszFormat;
+	std::transform(fmt.begin(), fmt.end(), fmt.begin(), ::toupper);
+	if (!isValidFormat(fmt)) return -2;
+
+	Lock();
+	int maxId = 0;
+	for (auto v : m_dataVariabels) {
+		if (v != nullptr && static_cast<int>(v->getVarialbleId()) > maxId) {
+			maxId = static_cast<int>(v->getVarialbleId());
+		}
+	}
+	outId = maxId + 1;
+
+	SERVO::CDataVariable* pNew = new SERVO::CDataVariable(std::to_string(outId).c_str(), pszName, fmt.c_str(), pszRemark ? pszRemark : "");
+	m_dataVariabels.push_back(pNew);
+	auto filepath = m_strDataVariableFilepath;
+	Unlock();
+
+	if (filepath.empty()) return -3;
+	return writeDataVariablesToFile(filepath);
+}
+
+int CHsmsPassive::updateDataVariable(int dvid, const char* pszName, const char* pszFormat, const char* pszRemark)
+{
+	if (pszName == nullptr || pszFormat == nullptr) return -1;
+	std::string fmt = pszFormat;
+	std::transform(fmt.begin(), fmt.end(), fmt.begin(), ::toupper);
+	if (!isValidFormat(fmt)) return -2;
+
+	Lock();
+	auto it = std::find_if(m_dataVariabels.begin(), m_dataVariabels.end(), [=](SERVO::CDataVariable* v) {
+		return v != nullptr && v->getVarialbleId() == (unsigned int)dvid;
+		});
+	if (it == m_dataVariabels.end()) {
+		Unlock();
+		return -4;
+	}
+	(*it)->setName(pszName);
+	(*it)->setFormat(fmt.c_str());
+	(*it)->setRemark(pszRemark ? pszRemark : "");
+	auto filepath = m_strDataVariableFilepath;
+	Unlock();
+
+	if (filepath.empty()) return -3;
+	return writeDataVariablesToFile(filepath);
+}
+
+int CHsmsPassive::writeVariablesToFile(const std::string& filepath)
+{
+	if (filepath.empty()) return -3;
+
+	CFile file;
+	if (!file.Open(filepath.c_str(), CFile::modeCreate | CFile::modeWrite | CFile::shareDenyNone)) {
+		return -3;
+	}
+
+	// header
+	const std::string headerAnsi = "SVID,SV Name,SV Format,SV Remark\r\n";
+	if (m_bVariableUtf8) {
+		if (m_bVariableUtf8Bom) {
+			const BYTE bom[3] = { 0xEF, 0xBB, 0xBF };
+			file.Write(bom, 3);
+		}
+		CStringA header = AnsiToUtf8(headerAnsi);
+		file.Write(header.GetString(), header.GetLength());
+	}
+	else {
+		file.Write(headerAnsi.data(), (UINT)headerAnsi.size());
+	}
+
+	for (auto v : m_variabels) {
+		if (v == nullptr) continue;
+		std::string lineAnsi;
+		lineAnsi.reserve(256);
+		lineAnsi += std::to_string(v->getVarialbleId());
+		lineAnsi.push_back(',');
+		lineAnsi += v->getName();
+		lineAnsi.push_back(',');
+		lineAnsi += SERVO::CVariable::formatToString(v->getFormat());
+		lineAnsi.push_back(',');
+		lineAnsi += v->getRemark();
+		lineAnsi.append("\r\n");
+
+		if (m_bVariableUtf8) {
+			CStringA outLine = AnsiToUtf8(lineAnsi);
+			file.Write(outLine.GetString(), outLine.GetLength());
+		}
+		else {
+			file.Write(lineAnsi.data(), (UINT)lineAnsi.size());
+		}
+	}
+	file.Close();
+
+	return 0;
+}
+
+int CHsmsPassive::writeDataVariablesToFile(const std::string& filepath)
+{
+	if (filepath.empty()) return -3;
+
+	CFile file;
+	if (!file.Open(filepath.c_str(), CFile::modeCreate | CFile::modeWrite | CFile::shareDenyNone)) {
+		return -3;
+	}
+
+	const std::string headerAnsi = "DVID,DV Name,DV Format,DV Remark\r\n";
+	if (m_bDataVariableUtf8) {
+		if (m_bDataVariableUtf8Bom) {
+			const BYTE bom[3] = { 0xEF, 0xBB, 0xBF };
+			file.Write(bom, 3);
+		}
+		CStringA header = AnsiToUtf8(headerAnsi);
+		file.Write(header.GetString(), header.GetLength());
+	}
+	else {
+		file.Write(headerAnsi.data(), (UINT)headerAnsi.size());
+	}
+
+	for (auto v : m_dataVariabels) {
+		if (v == nullptr) continue;
+		std::string lineAnsi;
+		lineAnsi.reserve(256);
+		lineAnsi += std::to_string(v->getVarialbleId());
+		lineAnsi.push_back(',');
+		lineAnsi += v->getName();
+		lineAnsi.push_back(',');
+		lineAnsi += SERVO::CVariable::formatToString(v->getFormat());
+		lineAnsi.push_back(',');
+		lineAnsi += v->getRemark();
+		lineAnsi.append("\r\n");
+
+		if (m_bDataVariableUtf8) {
+			CStringA outLine = AnsiToUtf8(lineAnsi);
+			file.Write(outLine.GetString(), outLine.GetLength());
+		}
+		else {
+			file.Write(lineAnsi.data(), (UINT)lineAnsi.size());
+		}
+	}
+	file.Close();
+	return 0;
 }
 
 int CHsmsPassive::loadReports(const char* pszFilepath)
 {
-	CStdioFile file;
-	if (!file.Open(pszFilepath, CFile::modeRead)) {
+	m_strReportFilepath = pszFilepath;
+	m_bReportUtf8 = false;
+	m_bReportUtf8Bom = false;
+	// 鍏煎 UTF-8/BOM 涓庢湰鍦扮紪鐮佽鍙�
+	CFile file;
+	if (!file.Open(pszFilepath, CFile::modeRead | CFile::shareDenyNone)) {
 		return -1;
 	}
 
-	std::regex pattern("^\\d+,\\(\\d+(,\\d+)*\\).*");  // 鍖归厤浠ユ暟瀛�+閫楀彿寮�澶寸殑瀛楃涓�
+	const ULONGLONG nLen = file.GetLength();
+	if (nLen == 0) {
+		return -1;
+	}
+
+	std::string buffer;
+	buffer.resize(static_cast<size_t>(nLen));
+	file.Read(buffer.data(), static_cast<UINT>(nLen));
+	file.Close();
+
+	const unsigned char* bytes = reinterpret_cast<const unsigned char*>(buffer.data());
+	size_t offset = 0;
+	CStringW content;
+
+	// UTF-8 BOM
+	if (nLen >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) {
+		offset = 3;
+		m_bReportUtf8 = true;
+		m_bReportUtf8Bom = true;
+	}
+
+	// UTF-16 LE BOM
+	if (nLen >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE) {
+		const wchar_t* wdata = reinterpret_cast<const wchar_t*>(buffer.data() + 2);
+		const size_t wlen = (nLen - 2) / sizeof(wchar_t);
+		content.SetString(wdata, static_cast<int>(wlen));
+	}
+	// UTF-16 BE BOM
+	else if (nLen >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF) {
+		const size_t wlen = (nLen - 2) / sizeof(wchar_t);
+		std::wstring temp;
+		temp.reserve(wlen);
+		for (size_t i = 0; i < wlen; ++i) {
+			wchar_t ch = static_cast<wchar_t>(bytes[2 + i * 2] << 8 | bytes[2 + i * 2 + 1]);
+			temp.push_back(ch);
+		}
+		content = temp.c_str();
+	}
+	else {
+		auto tryUtf8 = [&](size_t off) -> bool {
+			int need = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, buffer.data() + off,
+				static_cast<int>(buffer.size() - off), nullptr, 0);
+			if (need <= 0) return false;
+			std::wstring temp;
+			temp.resize(need);
+			MultiByteToWideChar(CP_UTF8, 0, buffer.data() + off,
+				static_cast<int>(buffer.size() - off), temp.data(), need);
+			content = temp.c_str();
+			m_bReportUtf8 = true;
+			return true;
+		};
+
+		if (!tryUtf8(offset)) {
+			int need = MultiByteToWideChar(CP_ACP, 0, buffer.data(), static_cast<int>(buffer.size()), nullptr, 0);
+			if (need > 0) {
+				std::wstring temp;
+				temp.resize(need);
+				MultiByteToWideChar(CP_ACP, 0, buffer.data(), static_cast<int>(buffer.size()), temp.data(), need);
+				content = temp.c_str();
+			}
+		}
+	}
+
+	if (content.IsEmpty()) {
+		return -1;
+	}
+
+	std::wregex pattern(L"^\\d+,\\(\\d+(,\\d+)*\\).*");  // 鍖归厤浠ユ暟瀛�+閫楀彿寮�澶寸殑瀛楃涓�
 	std::vector<SERVO::CReport*> reports;
 	int index;
-	CString strLine, strVariable;
-	CString strId;
-	while (file.ReadString(strLine)) {
-		if (!std::regex_match((LPTSTR)(LPCTSTR)strLine, pattern)) {
+	CStringW strLine, strVariable;
+	CStringW strId;
+	std::wstringstream ss(content.GetString());
+	auto narrowFromW = [](const CStringW& s) -> std::string {
+		int need = WideCharToMultiByte(CP_ACP, 0, s, -1, nullptr, 0, nullptr, nullptr);
+		if (need <= 0) return {};
+		std::string out(static_cast<size_t>(need - 1), '\0');
+		WideCharToMultiByte(CP_ACP, 0, s, -1, out.data(), need, nullptr, nullptr);
+		return out;
+	};
+	std::wstring line;
+	while (std::getline(ss, line, L'\n')) {
+		strLine = line.c_str();
+		strLine.Trim();
+		if (strLine.IsEmpty()) continue;
+		if (!std::regex_match(static_cast<LPCWSTR>(strLine), pattern)) {
 			continue;
 		}
 
-		index = strLine.Find(",", 0);
+		index = strLine.Find(L",", 0);
 		if (index < 0) continue;
 		strId = strLine.Left(index);
 		strVariable = strLine.Right(strLine.GetLength() - index - 1);
 		strVariable.Delete(0);
 		strVariable.Delete(strVariable.GetLength() - 1);
-		auto vids = parseVidList(strVariable);
+		CString strVariableA(narrowFromW(strVariable).c_str());
+		auto vids = parseVidList(strVariableA);
 
-		SERVO::CReport* pReport = new SERVO::CReport(atoi((LPTSTR)(LPCTSTR)strId), vids);
+		SERVO::CReport* pReport = new SERVO::CReport(_wtoi(strId), vids);
 		for (auto vid : vids) {
 			SERVO::CVariable* pVariable = getVariable(vid);
+			if (pVariable == nullptr) {
+				pVariable = getDataVariable(vid);
+			}
 			if (pVariable != nullptr) {
 				pReport->addVariable(pVariable);
 			}
@@ -380,13 +1214,23 @@
 	}
 	
 
-	file.Close();
 	return 0;
 }
 
 std::vector<SERVO::CReport*>& CHsmsPassive::getReports()
 {
 	return m_reports;
+}
+
+unsigned int CHsmsPassive::getMaxReportId() const
+{
+	unsigned int maxId = 0;
+	for (auto item : m_reports) {
+		if (item && item->getReportId() > maxId) {
+			maxId = item->getReportId();
+		}
+	}
+	return maxId;
 }
 
 SERVO::CReport* CHsmsPassive::getReport(int rptid)
@@ -413,52 +1257,248 @@
 	return false;
 }
 
-void CHsmsPassive::clearAllReport()
+int CHsmsPassive::deleteReport(int rptid)
 {
+	LOGI("<CHsmsPassive>deleteReport enter");
+	if (!removeReport(rptid)) {
+		return -1;
+	}
+	LOGI("<CHsmsPassive>delete Report. rptid=%d", rptid);
+
+	return writeReportsToFile(m_strReportFilepath);
+}
+
+int CHsmsPassive::addReport(int rptid, const std::vector<unsigned int>& vids)
+{
+	if (getReport(rptid) != nullptr) {
+		return -1;
+	}
+	SERVO::CReport* pReport = new SERVO::CReport(rptid, vids);
+	for (auto vid : vids) {
+		SERVO::CVariable* pVariable = getVariable((int)vid);
+		if (pVariable == nullptr) {
+			pVariable = getDataVariable((int)vid);
+		}
+		if (pVariable != nullptr) {
+			pReport->addVariable(pVariable);
+		}
+	}
+	m_reports.push_back(pReport);
+	return writeReportsToFile(m_strReportFilepath);
+}
+
+int CHsmsPassive::updateReport(int rptid, const std::vector<unsigned int>& vids)
+{
+	for (auto iter = m_reports.begin(); iter != m_reports.end(); ++iter) {
+		if ((*iter)->getReportId() == rptid) {
+			delete (*iter);
+			SERVO::CReport* pReport = new SERVO::CReport(rptid, vids);
+			for (auto vid : vids) {
+				SERVO::CVariable* pVariable = getVariable((int)vid);
+				if (pVariable == nullptr) {
+					pVariable = getDataVariable((int)vid);
+				}
+				if (pVariable != nullptr) {
+					pReport->addVariable(pVariable);
+				}
+			}
+			*iter = pReport;
+			return writeReportsToFile(m_strReportFilepath);
+		}
+	}
+	return -1;
+}
+
+void CHsmsPassive::clearAllReport(BOOL bSave/* = FALSE*/)
+{
+	LOGI("<CHsmsPassive>clearAllReport enter");
 	for (auto item : m_reports) {
 		delete item;
 	}
 	m_reports.clear();
+
+	if(bSave)
+		writeReportsToFile(m_strReportFilepath);
+}
+
+int CHsmsPassive::writeReportsToFile(const std::string& filepath)
+{
+	if (filepath.empty()) return -1;
+
+	CFile file;
+	if (!file.Open(filepath.c_str(), CFile::modeCreate | CFile::modeWrite)) {
+		return -1;
+	}
+
+	if (m_bReportUtf8 && m_bReportUtf8Bom) {
+		const BYTE bom[3] = { 0xEF, 0xBB, 0xBF };
+		file.Write(bom, 3);
+	}
+
+	// header
+	const std::string headerAnsi = "RPTID,(VID1,VID2,...)\r\n";	
+	if (m_bReportUtf8) {
+		CStringA header = AnsiToUtf8(headerAnsi);
+		file.Write(header.GetString(), header.GetLength());
+	}
+	else {
+		file.Write(headerAnsi.data(), (UINT)headerAnsi.size());
+	}
+
+	for (auto rpt : m_reports) {
+		if (rpt == nullptr) continue;
+		std::string line;
+		line.reserve(64);
+		line += std::to_string(rpt->getReportId());
+		line += ",(";
+
+		const auto& vids = rpt->getVids();
+		for (size_t i = 0; i < vids.size(); ++i) {
+			line += std::to_string(vids[i]);
+			if (i + 1 < vids.size()) {
+				line.push_back(',');
+			}
+		}
+		line += ")\r\n";
+
+		if (m_bReportUtf8) {
+			CStringA out = AnsiToUtf8(line);
+			file.Write(out.GetString(), out.GetLength());
+		}
+		else {
+			file.Write(line.data(), (UINT)line.size());
+		}
+	}
+
+	file.Close();
+	return 0;
 }
 
 int CHsmsPassive::loadCollectionEvents(const char* pszFilepath)
 {
-	CStdioFile file;
-	if (!file.Open(pszFilepath, CFile::modeRead)) {
+	m_strCollectionEventFilepath = pszFilepath;
+	m_bCollectionUtf8 = false;
+	m_bCollectionUtf8Bom = false;
+	CFile file;
+	if (!file.Open(pszFilepath, CFile::modeRead | CFile::shareDenyNone)) {
 		return -1;
 	}
 
-	std::regex pattern("^\\d+,[^,]*,[^,]*,\\(\\d+(,\\d+)*\\).*");  // 鍖归厤浠ユ暟瀛�+閫楀彿寮�澶寸殑瀛楃涓�
+	const ULONGLONG nLen = file.GetLength();
+	if (nLen == 0) {
+		return -1;
+	}
+
+	std::string buffer;
+	buffer.resize(static_cast<size_t>(nLen));
+	file.Read(buffer.data(), static_cast<UINT>(nLen));
+	file.Close();
+
+	const unsigned char* bytes = reinterpret_cast<const unsigned char*>(buffer.data());
+	size_t offset = 0;
+	CStringW content;
+
+	// UTF-8 BOM
+	if (nLen >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) {
+		offset = 3;
+		m_bCollectionUtf8 = true;
+		m_bCollectionUtf8Bom = true;
+	}
+
+	// UTF-16 LE BOM
+	if (nLen >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE) {
+		const wchar_t* wdata = reinterpret_cast<const wchar_t*>(buffer.data() + 2);
+		const size_t wlen = (nLen - 2) / sizeof(wchar_t);
+		content.SetString(wdata, static_cast<int>(wlen));
+	}
+	// UTF-16 BE BOM
+	else if (nLen >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF) {
+		const size_t wlen = (nLen - 2) / sizeof(wchar_t);
+		std::wstring temp;
+		temp.reserve(wlen);
+		for (size_t i = 0; i < wlen; ++i) {
+			wchar_t ch = static_cast<wchar_t>(bytes[2 + i * 2] << 8 | bytes[2 + i * 2 + 1]);
+			temp.push_back(ch);
+		}
+		content = temp.c_str();
+	}
+	else {
+		auto tryUtf8 = [&](size_t off) -> bool {
+			int need = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, buffer.data() + off,
+				static_cast<int>(buffer.size() - off), nullptr, 0);
+			if (need <= 0) return false;
+			std::wstring temp;
+			temp.resize(need);
+			MultiByteToWideChar(CP_UTF8, 0, buffer.data() + off,
+				static_cast<int>(buffer.size() - off), temp.data(), need);
+			content = temp.c_str();
+			m_bCollectionUtf8 = true;
+			return true;
+		};
+
+		if (!tryUtf8(offset)) {
+			int need = MultiByteToWideChar(CP_ACP, 0, buffer.data(), static_cast<int>(buffer.size()), nullptr, 0);
+			if (need > 0) {
+				std::wstring temp;
+				temp.resize(need);
+				MultiByteToWideChar(CP_ACP, 0, buffer.data(), static_cast<int>(buffer.size()), temp.data(), need);
+				content = temp.c_str();
+			}
+		}
+	}
+
+	if (content.IsEmpty()) {
+		return -1;
+	}
+
+	// 鍏佽 Attached RPTID 涓虹┖锛�()
+	std::wregex pattern(L"^\\d+,[^,]*,[^,]*,\\(\\d*(,\\d+)*\\).*");  // 鍖归厤浠ユ暟瀛�+閫楀彿寮�澶寸殑瀛楃涓�
 	std::vector<SERVO::CCollectionEvent*> events;
 	int index, last;
-	CString strLine, strRPTIDs;
-	CString strId, strName, strDescription;
-	while (file.ReadString(strLine)) {
-		if (!std::regex_match((LPTSTR)(LPCTSTR)strLine, pattern)) {
+	CStringW strLine, strRPTIDs;
+	CStringW strId, strName, strDescription;
+	std::wstringstream ss(content.GetString());
+	auto narrowFromW = [](const CStringW& s) -> std::string {
+		int need = WideCharToMultiByte(CP_ACP, 0, s, -1, nullptr, 0, nullptr, nullptr);
+		if (need <= 0) return {};
+		std::string out(static_cast<size_t>(need - 1), '\0');
+		WideCharToMultiByte(CP_ACP, 0, s, -1, out.data(), need, nullptr, nullptr);
+		return out;
+	};
+	std::wstring line;
+	while (std::getline(ss, line, L'\n')) {
+		strLine = line.c_str();
+		strLine.Trim();
+		if (strLine.IsEmpty()) continue;
+		if (!std::regex_match(static_cast<LPCWSTR>(strLine), pattern)) {
 			continue;
 		}
 
 		last = 0;
-		index = strLine.Find(",", last);
+		index = strLine.Find(L",", last);
 		if (index < 0) continue;
 		strId = strLine.Left(index);
 		last = index + 1;
 
-		index = strLine.Find(",", last);
+		index = strLine.Find(L",", last);
 		if (index < 0) continue;
 		strName = strLine.Mid(last, index - last);
 		last = index + 1;
 
-		index = strLine.Find(",", last);
+		index = strLine.Find(L",", last);
 		if (index < 0) continue;
 		strDescription = strLine.Mid(last, index - last);
 		strRPTIDs = strLine.Right(strLine.GetLength() - index - 1);
 		strRPTIDs.Delete(0);
 		strRPTIDs.Delete(strRPTIDs.GetLength() - 1);
-		auto prtids = parseVidList(strRPTIDs);
+		CString strRPTIDsA(narrowFromW(strRPTIDs).c_str());
+		auto prtids = parseVidList(strRPTIDsA);
+
+		std::string sName = narrowFromW(strName);
+		std::string sDesc = narrowFromW(strDescription);
 
 		SERVO::CCollectionEvent* pEvent = new SERVO::CCollectionEvent(
-			atoi(strId), (LPTSTR)(LPCTSTR)strName, (LPTSTR)(LPCTSTR)strDescription, prtids);
+			_wtoi(strId), sName.c_str(), sDesc.c_str(), prtids);
 		for (auto rptid : prtids) {
 			SERVO::CReport* pReport = getReport(rptid);
 			if (pReport != nullptr) {
@@ -474,15 +1514,70 @@
 			m_collectionEvents.push_back(item);
 		}
 	}
-
-
-	file.Close();
 	return 0;
 }
 
 std::vector<SERVO::CCollectionEvent*>& CHsmsPassive::getCollectionEvents()
 {
 	return m_collectionEvents;
+}
+
+unsigned int CHsmsPassive::getMaxCollectionEventId() const
+{
+	unsigned int maxId = 0;
+	for (auto item : m_collectionEvents) {
+		if (item && item->getEventId() > maxId) {
+			maxId = item->getEventId();
+		}
+	}
+	return maxId;
+}
+
+int CHsmsPassive::deleteCollectionEvent(unsigned short CEID)
+{
+	for (auto iter = m_collectionEvents.begin(); iter != m_collectionEvents.end(); ++iter) {
+		if ((*iter)->getEventId() == CEID) {
+			delete (*iter);
+			m_collectionEvents.erase(iter);
+			return writeCollectionEventsToFile(m_strCollectionEventFilepath);
+		}
+	}
+	return -1;
+}
+
+int CHsmsPassive::addCollectionEvent(unsigned int CEID, const char* name, const char* desc, const std::vector<unsigned int>& rptids)
+{
+	if (getEvent((unsigned short)CEID) != nullptr) {
+		return -1;
+	}
+	auto* pEvent = new SERVO::CCollectionEvent(CEID, name, desc, const_cast<std::vector<unsigned int>&>(rptids));
+	for (auto rptid : rptids) {
+		SERVO::CReport* pReport = getReport((int)rptid);
+		if (pReport != nullptr) {
+			pEvent->addReport(pReport);
+		}
+	}
+	m_collectionEvents.push_back(pEvent);
+	return writeCollectionEventsToFile(m_strCollectionEventFilepath);
+}
+
+int CHsmsPassive::updateCollectionEvent(unsigned int CEID, const char* name, const char* desc, const std::vector<unsigned int>& rptids)
+{
+	for (auto iter = m_collectionEvents.begin(); iter != m_collectionEvents.end(); ++iter) {
+		if ((*iter)->getEventId() == CEID) {
+			delete (*iter);
+			auto* pEvent = new SERVO::CCollectionEvent(CEID, name, desc, const_cast<std::vector<unsigned int>&>(rptids));
+			for (auto rptid : rptids) {
+				SERVO::CReport* pReport = getReport((int)rptid);
+				if (pReport != nullptr) {
+					pEvent->addReport(pReport);
+				}
+			}
+			*iter = pEvent;
+			return writeCollectionEventsToFile(m_strCollectionEventFilepath);
+		}
+	}
+	return -1;
 }
 
 void CHsmsPassive::clearAllCollectionEvent()
@@ -526,6 +1621,62 @@
 	return result;
 }
 
+int CHsmsPassive::writeCollectionEventsToFile(const std::string& filepath)
+{
+	if (filepath.empty()) return -1;
+
+	CFile file;
+	if (!file.Open(filepath.c_str(), CFile::modeCreate | CFile::modeWrite)) {
+		return -1;
+	}
+
+	if (m_bCollectionUtf8 && m_bCollectionUtf8Bom) {
+		const BYTE bom[3] = { 0xEF, 0xBB, 0xBF };
+		file.Write(bom, 3);
+	}
+
+	const std::string headerAnsi = "CEID,CE Name,Descriptions,Attached RPTID\r\n";
+	if (m_bCollectionUtf8) {
+		CStringA header = AnsiToUtf8(headerAnsi);
+		file.Write(header.GetString(), header.GetLength());
+	}
+	else {
+		file.Write(headerAnsi.data(), (UINT)headerAnsi.size());
+	}
+
+	for (auto ev : m_collectionEvents) {
+		if (ev == nullptr) continue;
+		std::string line;
+		line.reserve(128);
+		line += std::to_string(ev->getEventId());
+		line.push_back(',');
+		line += ev->getName();
+		line.push_back(',');
+		line += ev->getDescription();
+		line.push_back(',');
+		line.push_back('(');
+		auto rptIds = ev->getReportIds();
+		for (size_t i = 0; i < rptIds.size(); ++i) {
+			line += std::to_string(rptIds[i]);
+			if (i + 1 < rptIds.size()) {
+				line.push_back(',');
+			}
+		}
+		line += ")\r\n";
+
+		if (m_bCollectionUtf8) {
+			CStringA out = AnsiToUtf8(line);
+			file.Write(out.GetString(), out.GetLength());
+		}
+		else {
+			file.Write(line.data(), (UINT)line.size());
+		}
+	}
+
+	file.Close();
+	return 0;
+}
+
 int CHsmsPassive::init(CModel* pModel, const char* pszName, unsigned int port)
 {
 	m_pModel = pModel;
@@ -549,7 +1700,7 @@
 		*/
 	};
 	auto onRecvSysMessage = [&](void* pFrom, IMessage* pMessage) -> void {
-		LOGI("<HSMS>onRecvSysMessage:sessionId:%d, sType:%d systemBytes:%d",
+		LOGI("<HSMS>[Received]sessionId:%d, sType:%d systemBytes:%d",
 		pMessage->getHeader()->sessionId, pMessage->getHeader()->sType, pMessage->getHeader()->systemBytes);
 		onRecvMsg(pMessage);
 		if (MSG_LINKTEST_REQ == pMessage->getHeader()->sType) {
@@ -576,13 +1727,22 @@
 		HEADER* pHeader = pMessage->getHeader();
 		int nStream = (pHeader->stream & 0x7F);
 
-		LOGI("<HSMS>鏀跺埌娑堟伅 S%dF%d", nStream, pHeader->function);
+		LogSecsMessageBrief("<HSMS>[Received]", pMessage);
 		if (nStream == 1 && pHeader->function == 1) {
 			// S1F1
 			replyAreYouThere(pMessage);
 		}
 		else if (nStream == 1 && pHeader->function == 3) {
 			replySelectedEquipmentStatusData(pMessage);
+		}
+		else if (nStream == 1 && pHeader->function == 11) {
+			replyStatusVariableNamelistRequest(pMessage);
+		}
+		else if (nStream == 1 && pHeader->function == 21) {
+			replyDataVariableNamelistRequest(pMessage);
+		}
+		else if (nStream == 1 && pHeader->function == 23) {
+			replyCollectionEventNamelistRequest(pMessage);
 		}
 		else if (nStream == 1 && pHeader->function == 13) {
 			replyEstablishCommunications(pMessage);
@@ -629,6 +1789,12 @@
 		else if (nStream == 7 && pHeader->function == 19) {
 			replyQueryPPIDList(pMessage);
 		}
+		else if (nStream == 7 && pHeader->function == 17) {
+			replyDeletePPID(pMessage);
+		}
+		else if (nStream == 7 && pHeader->function == 5) {
+			replyProcessProgramRequest(pMessage);
+		}
 		else if (nStream == 10 && pHeader->function == 3) {
 			replyTerminalDisplay(pMessage);
 		}
@@ -668,7 +1834,12 @@
 		return -1;
 	}
 
-	int nBufSize = file.GetLength();
+	ULONGLONG len = file.GetLength();
+	if (len > INT_MAX) {
+		file.Close();
+		return -1;
+	}
+	int nBufSize = static_cast<int>(len);
 	char* pszBuffer = new char[nBufSize];
 	file.Read(pszBuffer, nBufSize);
 	file.Close();
@@ -721,25 +1892,93 @@
 int CHsmsPassive::serialize(char* pszBuffer, int nBufferSize)
 {
 	int index = 0;
+	const auto calcSpoolCfgSize = [&]() -> int {
+		// magic(4) + ver(2) + enabled(1) + mapSize(4) + entries...
+		int sz = 0;
+		sz += 4; // 'SPOL'
+		sz += 2; // version
+		sz += 1; // enabled
+		sz += 4; // map size
+		for (const auto& kv : m_spoolBlacklistByStream) {
+			sz += 2; // streamId (U16)
+			sz += 4; // fn count (U32)
+			sz += static_cast<int>(kv.second.size()) * 2; // fn ids (U16 each)
+		}
+		return sz;
+	};
 	if (pszBuffer == nullptr) {
 		index += sizeof(int);
 		for (auto item : m_listActionSpooling) {
-			index += item->serialize(pszBuffer, nBufferSize);
+			if (item == nullptr || item->getSendMessage() == nullptr) {
+				LOGE("<HSMS>skip spooling item: null send message");
+				continue;
+			}
+			int nRet = item->serialize(nullptr, 0);
+			if (nRet <= 0) {
+				LOGE("<HSMS>skip spooling item: serialize failed");
+				continue;
+			}
+			index += nRet;
 		}
+
+		index += calcSpoolCfgSize();
 
 		return index;
 	}
 	else {
-		int nTemp, nRet;
+		int nTemp = 0;
+		int nRet = 0;
 
-		nTemp = (int)m_listActionSpooling.size();
+		for (auto item : m_listActionSpooling) {
+			if (item == nullptr || item->getSendMessage() == nullptr) {
+				continue;
+			}
+			if (item->serialize(nullptr, 0) > 0) {
+				++nTemp;
+			}
+		}
+
 		memcpy(&pszBuffer[index], &nTemp, sizeof(int));
 		index += sizeof(int);
 
 		for (auto item : m_listActionSpooling) {
+			if (item == nullptr || item->getSendMessage() == nullptr) {
+				LOGE("<HSMS>skip spooling item: null send message");
+				continue;
+			}
 			nRet = item->serialize(&pszBuffer[index], nBufferSize);
-			if (nRet <= 0) break;
+			if (nRet <= 0) {
+				LOGE("<HSMS>skip spooling item: serialize failed");
+				continue;
+			}
 			index += nRet;
+		}
+
+		// Append spooling config (backward compatible via magic+version)
+		auto writeU32 = [&](uint32_t v) {
+			memcpy(&pszBuffer[index], &v, sizeof(v));
+			index += sizeof(v);
+		};
+		auto writeU16 = [&](uint16_t v) {
+			memcpy(&pszBuffer[index], &v, sizeof(v));
+			index += sizeof(v);
+		};
+		auto writeU8 = [&](uint8_t v) {
+			memcpy(&pszBuffer[index], &v, sizeof(v));
+			index += sizeof(v);
+		};
+
+		const uint32_t magic = 0x4C4F5053; // 'SPOL' little-endian
+		writeU32(magic);
+		writeU16(1); // version
+		writeU8(m_spoolingEnabled ? 1 : 0);
+		writeU32(static_cast<uint32_t>(m_spoolBlacklistByStream.size()));
+		for (const auto& kv : m_spoolBlacklistByStream) {
+			writeU16(static_cast<uint16_t>(kv.first));
+			writeU32(static_cast<uint32_t>(kv.second.size()));
+			for (const auto& fn : kv.second) {
+				writeU16(static_cast<uint16_t>(fn));
+			}
 		}
 
 		return index;
@@ -757,12 +1996,61 @@
 	for (int i = 0; i < nTemp; i++) {
 		CHsmsAction* pAction = new CHsmsAction();
 		nRet = pAction->unserialize(&pszBuffer[index], nBufferSize - index);
-		if (nRet <= 0) break;
+		if (nRet <= 0 || pAction->getSendMessage() == nullptr) {
+			delete pAction;
+			break;
+		}
 		index += nRet;
 		m_listActionSpooling.push_back(pAction);
 	}
 
-	return index + nRet;
+	// Parse optional spooling config tail (magic+version). If absent, keep defaults.
+	const auto remaining = nBufferSize - index;
+	if (remaining >= 4) {
+		uint32_t magic = 0;
+		memcpy(&magic, &pszBuffer[index], sizeof(magic));
+		if (magic == 0x4C4F5053) { // 'SPOL'
+			index += 4;
+			if (nBufferSize - index >= 2 + 1 + 4) {
+				uint16_t ver = 0;
+				memcpy(&ver, &pszBuffer[index], sizeof(ver));
+				index += 2;
+				if (ver >= 1) {
+					uint8_t enabled = 1;
+					memcpy(&enabled, &pszBuffer[index], sizeof(enabled));
+					index += 1;
+					m_spoolingEnabled = (enabled != 0);
+
+					uint32_t mapSize = 0;
+					memcpy(&mapSize, &pszBuffer[index], sizeof(mapSize));
+					index += 4;
+
+					m_spoolBlacklistByStream.clear();
+					for (uint32_t mi = 0; mi < mapSize; ++mi) {
+						if (nBufferSize - index < 2 + 4) break;
+						uint16_t streamId = 0;
+						memcpy(&streamId, &pszBuffer[index], sizeof(streamId));
+						index += 2;
+						uint32_t fnCount = 0;
+						memcpy(&fnCount, &pszBuffer[index], sizeof(fnCount));
+						index += 4;
+
+						auto& setRef = m_spoolBlacklistByStream[streamId];
+						setRef.clear();
+						for (uint32_t fi = 0; fi < fnCount; ++fi) {
+							if (nBufferSize - index < 2) break;
+							uint16_t fn = 0;
+							memcpy(&fn, &pszBuffer[index], sizeof(fn));
+							index += 2;
+							setRef.insert(fn);
+						}
+					}
+				}
+			}
+		}
+	}
+
+	return index;
 }
 
 unsigned CHsmsPassive::OnCimWork()
@@ -781,27 +2069,57 @@
 		Unlock();
 
 		while (!list.empty()) {
+			CHsmsAction* pAction = nullptr;
 			Lock();
-			CHsmsAction* pAction = list.front();
-			if (m_pPassive == NULL || STATE::SELECTED != m_pPassive->getState()) {
-				m_listActionSpooling.push_back(pAction);
-				Unlock();
-				continue;
-			}
+			pAction = list.front();
 			Unlock();
 			list.pop_front();
+
+			Lock();
+			const bool selected = (m_pPassive != NULL && STATE::SELECTED == m_pPassive->getState());
+			Unlock();
+			if (!selected) {
+				IMessage* pMsg = pAction->getSendMessage();
+				if (pMsg == NULL) {
+					LOGE("<HSMS>spooling drop: null send message");
+					delete pAction;
+					continue;
+				}
+				uint8_t streamId = 0;
+				uint8_t functionId = 0;
+				if (pMsg && pMsg->getHeader()) {
+					streamId = static_cast<uint8_t>(pMsg->getHeader()->stream & 0x7F);
+					functionId = static_cast<uint8_t>(pMsg->getHeader()->function & 0xFF);
+				}
+				if (shouldSpool(streamId, functionId)) {
+					Lock();
+					m_listActionSpooling.push_back(pAction);
+					Unlock();
+				}
+				else {
+					LOGI("<HSMS>spooling disabled for S%dF%d, drop action", (int)streamId, (int)functionId);
+					delete pAction;
+				}
+				continue;
+			}
 			TRACE("OnCimWork 004.\n");
 
 			if (pAction->isNeedWaitReply()) {
 				// 濡傛灉闇�瑕佺瓑寰呭洖澶�
+				IMessage* pMessage = pAction->getSendMessage();
+				if (pMessage == NULL) {
+					LOGE("<HSMS>drop action: null send message");
+					delete pAction;
+					continue;
+				}
 				Lock();
 				m_pActiveAction = pAction;
-				IMessage* pMessage = pAction->getSendMessage();
 				Unlock();
 
 				ASSERT(pMessage);
 				m_pPassive->sendMessage(pMessage);
-				LOGI("<HSMS> [SEND] SysByte=%u sessionId:%d", pMessage->getHeader()->systemBytes, pMessage->getHeader()->sessionId);
+				LOGI("<HSMS>[SEND]SysByte=%u sessionId:%d", pMessage->getHeader()->systemBytes, pMessage->getHeader()->sessionId);
+				LogSecsMessageBrief("<HSMS>[SEND]", pMessage);
 
 				int nRet = WaitForSingleObject(pAction->getEvent(), pAction->getTimeout() * 1000);
 				if (nRet == WAIT_TIMEOUT) {
@@ -819,14 +2137,20 @@
 				Unlock();
 			}
 			else {
+				IMessage* pMessage = pAction->getSendMessage();
+				if (pMessage == NULL) {
+					LOGE("<HSMS>drop action: null send message");
+					delete pAction;
+					continue;
+				}
 				Lock();
 				m_listActionSent.push_back(pAction);
-				IMessage* pMessage = pAction->getSendMessage();
 				Unlock();
 
 				ASSERT(pMessage);
 				m_pPassive->sendMessage(pMessage);
-				LOGI("<HSMS> [SEND] SysByte=%u sessionId:%d", pMessage->getHeader()->systemBytes, pMessage->getHeader()->sessionId);
+				LOGI("<HSMS>[SEND]SysByte=%u sessionId:%d", pMessage->getHeader()->systemBytes, pMessage->getHeader()->sessionId);
+				LogSecsMessageBrief("<HSMS>[SEND]", pMessage);
 
 			}
 		}
@@ -848,7 +2172,9 @@
 	ISECS2Item* pItem = pMessage->getBody();
 	pItem->setBinary((const char*)&ack, 1, pszAckName);
 	m_pPassive->sendMessage(pMessage);
-	LOGI("<HSMS>[SECS Msg SEND]S%dF%d (SysByte=%u)", s, f, systemBytes);
+	LOGI("<HSMS>[SEND]sessionId:%d, sType:%d systemBytes:%d",
+		pMessage->getHeader()->sessionId, pMessage->getHeader()->sType, pMessage->getHeader()->systemBytes);
+	LogSecsMessageBrief("<HSMS>[SEND]", pMessage);
 	HSMS_Destroy1Message(pMessage);
 }
 
@@ -861,11 +2187,15 @@
 
 	Lock();
 	CHsmsAction* pAction = new CHsmsAction(ACTION_HELLO, FALSE, m_nActionTimeout);
-	m_listAction.push_back(pAction);
 	IMessage* pMessage = NULL;
-	HSMS_Create1Message(pMessage, m_nSessionId, 1 | REPLY, 1, ++m_nSystemByte);
-	ASSERT(pMessage);
+	if (HSMS_Create1Message(pMessage, m_nSessionId, 1 | REPLY, 1, ++m_nSystemByte) != 0 || pMessage == NULL) {
+		LOGE("<HSMS>S1F1 create message failed");
+		delete pAction;
+		Unlock();
+		return ER_CREATED_MESSAGE;
+	}
 	pAction->setSendMessage(pMessage);
+	m_listAction.push_back(pAction);
 
 	SetEvent(m_hCimWorkEvent);
 	Unlock();
@@ -889,7 +2219,9 @@
 	pItem->addItem(m_strEquipmentModelType.c_str(), "MDLN");
 	pItem->addItem(m_strSoftRev.c_str(), "SOFTREV");
 	m_pPassive->sendMessage(pMessage);
-	LOGI("<HSMS>[SECS Msg SEND]S1F2 (SysByte=%u)", pMessage->getHeader()->systemBytes);
+	LOGI("<HSMS>[SEND]sessionId:%d, sType:%d systemBytes:%d",
+		pMessage->getHeader()->sessionId, pMessage->getHeader()->sType, pMessage->getHeader()->systemBytes);
+	LogSecsMessageBrief("<HSMS>[SEND]", pMessage);
 	HSMS_Destroy1Message(pMessage);
 
 	return 0;
@@ -950,7 +2282,9 @@
 	pList->addItem(m_strEquipmentModelType.c_str(), "MDLN");
 	pList->addItem(m_strSoftRev.c_str(), "SOFTREV");
 	m_pPassive->sendMessage(pMessage);
-	LOGI("<HSMS>[SECS Msg SEND]%s", pMessage->toString());
+	LOGI("<HSMS>[SEND]sessionId:%d, sType:%d systemBytes:%d",
+		pMessage->getHeader()->sessionId, pMessage->getHeader()->sType, pMessage->getHeader()->systemBytes);
+	LogSecsMessageBrief("<HSMS>[SEND]", pMessage);
 	HSMS_Destroy1Message(pMessage);
 
 	return 0;
@@ -968,18 +2302,25 @@
 	ASSERT(pMessage);
 
 	unsigned char SVU1 = 0;
-	unsigned int SVID = 0;
+	unsigned short 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;
+	if (!pBody->getSubItemU2(0, SVID)) {
+		// also accept I2 or U4 to be tolerant with host implementations
+		if (!pBody->getSubItemI2(0, (short&)SVID)) {
+			unsigned int svidU4 = 0;
+			if (!pBody->getSubItemU4(0, svidU4)) {
+				pMessage->getBody()->addU1Item(SVU1, "SV");
+				goto MYREPLY;
+			}
+			SVID = static_cast<unsigned short>(svidU4);
+		}
 	}
 
-	SERVO::CVariable* pVariable = getVariable(SVID);
+	SERVO::CVariable* pVariable = getVariable((int)SVID);
 	if (pVariable == nullptr) {
 		pMessage->getBody()->addU1Item(SVU1, "SV");
 		goto MYREPLY;
@@ -988,12 +2329,273 @@
 
 MYREPLY:
 	m_pPassive->sendMessage(pMessage);
-	LOGI("<HSMS>[SECS Msg SEND]%s", pMessage->toString());
+	LOGI("<HSMS>[SEND]sessionId:%d, sType:%d systemBytes:%d",
+		pMessage->getHeader()->sessionId, pMessage->getHeader()->sType, pMessage->getHeader()->systemBytes);
+	LogSecsMessageBrief("<HSMS>[SEND]", pMessage);
 	HSMS_Destroy1Message(pMessage);
 
 
 
 	return 0;
+}
+
+// S1F11
+int CHsmsPassive::replyStatusVariableNamelistRequest(IMessage* pRecv)
+{
+	if (m_pPassive == NULL || STATE::SELECTED != m_pPassive->getState()) {
+		return ER_NOTSELECT;
+	}
+
+	std::vector<unsigned short> reqIds;
+	ISECS2Item* pBody = pRecv->getBody();
+	if (pBody != nullptr && pBody->getType() == SITYPE::L) {
+		const int sz = pBody->getSubItemSize();
+		for (int i = 0; i < sz; ++i) {
+			unsigned short id = 0;
+			if (pBody->getSubItemU2(i, id)) {
+				reqIds.push_back(id);
+			}
+		}
+	}
+
+	// Build response list items: {L:3 SVID, SVNAME, UNITS}
+	std::vector<unsigned short> svids;
+	std::set<unsigned short> requested(reqIds.begin(), reqIds.end());
+	Lock();
+	if (reqIds.empty()) {
+		for (auto v : m_variabels) {
+			svids.push_back(static_cast<unsigned short>(v->getVarialbleId()));
+		}
+	}
+	else {
+		// include requested IDs (existing + unknown marker)
+		for (auto id : requested) {
+			svids.push_back(id);
+		}
+	}
+	Unlock();
+
+	IMessage* pMessage = NULL;
+	HSMS_Create1Message(pMessage, m_nSessionId, 1, 12, pRecv->getHeader()->systemBytes);
+	ASSERT(pMessage);
+
+	ISECS2Item* pList = pMessage->getBody(); // Body is L[n] of {SVID, SVNAME, UNITS}
+	for (auto id : svids) {
+		ISECS2Item* pEntry = pList->addItem();
+		pEntry->addU2Item(id, "SVID");
+		SERVO::CVariable* v = getVariable((int)id);
+		if (v != nullptr) {
+			pEntry->addItem(v->getName().c_str(), "SVNAME");
+			// Use remark as UNITS if provided; empty string if none.
+			pEntry->addItem(v->getRemark().c_str(), "UNITS");
+		}
+		else {
+			// Unknown SVID: A:0 for name/units
+			pEntry->addItem("", "SVNAME");
+			pEntry->addItem("", "UNITS");
+		}
+	}
+
+	m_pPassive->sendMessage(pMessage);
+	LOGI("<HSMS>[SEND]sessionId:%d, sType:%d systemBytes:%d",
+		pMessage->getHeader()->sessionId, pMessage->getHeader()->sType, pMessage->getHeader()->systemBytes);
+	LogSecsMessageBrief("<HSMS>[SEND]", pMessage);
+	HSMS_Destroy1Message(pMessage);
+
+	return ER_NOERROR;
+}
+
+int CHsmsPassive::writeEquipmentConstantsToFile(const std::string& filepath)
+{
+	if (filepath.empty()) return -1;
+	CFile file;
+	if (!file.Open(filepath.c_str(), CFile::modeCreate | CFile::modeWrite | CFile::shareDenyNone)) {
+		return -1;
+	}
+	const std::string headerAnsi = "ECID,EC Name,EC Format,EC Remark,Default Value\r\n";
+	if (m_bEquipmentConstantUtf8) {
+		if (m_bEquipmentConstantUtf8Bom) {
+			const BYTE bom[3] = { 0xEF, 0xBB, 0xBF };
+			file.Write(bom, 3);
+		}
+		CStringA header = AnsiToUtf8(headerAnsi);
+		file.Write(header.GetString(), header.GetLength());
+	}
+	else {
+		file.Write(headerAnsi.data(), (UINT)headerAnsi.size());
+	}
+	for (const auto& e : m_equipmentConstants) {
+		std::string line;
+		line.reserve(128);
+		line += std::to_string(e.id);
+		line.push_back(',');
+		line += e.name;
+		line.push_back(',');
+		line += e.format;
+		line.push_back(',');
+		line += e.remark;
+		line.push_back(',');
+		line += e.value;
+		line.append("\r\n");
+		if (m_bEquipmentConstantUtf8) {
+			CStringA out = AnsiToUtf8(line);
+			file.Write(out.GetString(), out.GetLength());
+		}
+		else {
+			file.Write(line.data(), (UINT)line.size());
+		}
+	}
+	file.Close();
+	return 0;
+}
+// S1F21/S1F22 - Data Variable Namelist
+int CHsmsPassive::replyDataVariableNamelistRequest(IMessage* pRecv)
+{
+	if (m_pPassive == NULL || STATE::SELECTED != m_pPassive->getState()) {
+		return ER_NOTSELECT;
+	}
+
+	std::vector<unsigned short> reqIds;
+	ISECS2Item* pBody = pRecv->getBody();
+	if (pBody != nullptr && pBody->getType() == SITYPE::L) {
+		const int sz = pBody->getSubItemSize();
+		for (int i = 0; i < sz; ++i) {
+			unsigned short id = 0;
+			if (pBody->getSubItemU2(i, id)) {
+				reqIds.push_back(id);
+			}
+		}
+	}
+
+	std::vector<unsigned short> dvids;
+	std::set<unsigned short> requested(reqIds.begin(), reqIds.end());
+	Lock();
+	if (reqIds.empty()) {
+		for (auto v : m_dataVariabels) {
+			if (v) dvids.push_back(static_cast<unsigned short>(v->getVarialbleId()));
+		}
+	}
+	else {
+		for (auto id : requested) dvids.push_back(id);
+	}
+	Unlock();
+
+	IMessage* pMessage = NULL;
+	HSMS_Create1Message(pMessage, m_nSessionId, 1, 22, pRecv->getHeader()->systemBytes);
+	ASSERT(pMessage);
+
+	ISECS2Item* pList = pMessage->getBody(); // L[n] of {DVID, DVNAME, UNITS}
+	for (auto id : dvids) {
+		ISECS2Item* pEntry = pList->addItem();
+		pEntry->addU2Item(id, "DVID");
+		SERVO::CDataVariable* v = getDataVariable((int)id);
+		if (v != nullptr) {
+			pEntry->addItem(v->getName().c_str(), "DVNAME");
+			pEntry->addItem(v->getRemark().c_str(), "UNITS");
+		}
+		else {
+			pEntry->addItem("", "DVNAME");
+			pEntry->addItem("", "UNITS");
+		}
+	}
+
+	m_pPassive->sendMessage(pMessage);
+	LOGI("<HSMS>[SEND]sessionId:%d, sType:%d systemBytes:%d",
+		pMessage->getHeader()->sessionId, pMessage->getHeader()->sType, pMessage->getHeader()->systemBytes);
+	LogSecsMessageBrief("<HSMS>[SEND]", pMessage);
+	HSMS_Destroy1Message(pMessage);
+
+	return ER_NOERROR;
+}
+
+// S1F23
+int CHsmsPassive::replyCollectionEventNamelistRequest(IMessage* pRecv)
+{
+	if (m_pPassive == NULL || STATE::SELECTED != m_pPassive->getState()) {
+		return ER_NOTSELECT;
+	}
+
+	std::vector<unsigned short> reqIds;
+	ISECS2Item* pBody = pRecv->getBody();
+	if (pBody != nullptr && pBody->getType() == SITYPE::L) {
+		const int sz = pBody->getSubItemSize();
+		for (int i = 0; i < sz; ++i) {
+			unsigned short id = 0;
+			if (pBody->getSubItemU2(i, id)) {
+				reqIds.push_back(id);
+			}
+		}
+	}
+
+	struct CEInfo {
+		unsigned short id{ 0 };
+		std::string name;
+		std::vector<unsigned short> vids;
+	};
+	std::vector<CEInfo> ceInfos;
+	{
+		Lock();
+		if (reqIds.empty()) {
+			for (auto e : m_collectionEvents) {
+				if (e == nullptr) continue;
+				CEInfo info;
+				info.id = static_cast<unsigned short>(e->getEventId());
+				info.name = e->getName();
+				std::set<unsigned short> vidSet;
+				for (auto rpt : e->getReports()) {
+					if (rpt == nullptr) continue;
+					for (auto vid : rpt->getVids()) {
+						vidSet.insert(static_cast<unsigned short>(vid));
+					}
+				}
+				info.vids.assign(vidSet.begin(), vidSet.end());
+				ceInfos.push_back(std::move(info));
+			}
+		}
+		else {
+			for (auto id : reqIds) {
+				CEInfo info;
+				info.id = id;
+				SERVO::CCollectionEvent* e = getEvent(id);
+				if (e != nullptr) {
+					info.name = e->getName();
+					std::set<unsigned short> vidSet;
+					for (auto rpt : e->getReports()) {
+						if (rpt == nullptr) continue;
+						for (auto vid : rpt->getVids()) {
+							vidSet.insert(static_cast<unsigned short>(vid));
+						}
+					}
+					info.vids.assign(vidSet.begin(), vidSet.end());
+				}
+				ceInfos.push_back(std::move(info));
+			}
+		}
+		Unlock();
+	}
+
+	IMessage* pMessage = NULL;
+	HSMS_Create1Message(pMessage, m_nSessionId, 1, 24, pRecv->getHeader()->systemBytes);
+	ASSERT(pMessage);
+
+	ISECS2Item* pList = pMessage->getBody(); // Body is L[n] of {CEID, CENAME, L[VIDs]}
+	for (const auto& info : ceInfos) {
+		ISECS2Item* pEntry = pList->addItem();
+		pEntry->addU2Item(info.id, "CEID");
+		pEntry->addItem(info.name.c_str(), "CENAME"); // empty if unknown
+		ISECS2Item* pVidList = pEntry->addItem();
+		for (auto vid : info.vids) {
+			pVidList->addU2Item(vid, "VID");
+		}
+	}
+
+	m_pPassive->sendMessage(pMessage);
+	LOGI("<HSMS>[SEND]sessionId:%d, sType:%d systemBytes:%d",
+		pMessage->getHeader()->sessionId, pMessage->getHeader()->sType, pMessage->getHeader()->systemBytes);
+	LogSecsMessageBrief("<HSMS>[SEND]", pMessage);
+	HSMS_Destroy1Message(pMessage);
+
+	return ER_NOERROR;
 }
 
 // S2F13
@@ -1004,32 +2606,42 @@
 	}
 
 
-	// 瑕佽幏鍙栫殑甯搁噺琛ㄨ〃
-	BOOL bCheckData = FALSE;
 	std::vector<EQConstant> eqcs;
 	{
 		ISECS2Item* pItem = pRecv->getBody();
-		int ecidSize = pItem->getSubItemSize();
+		const int ecidSize = pItem ? pItem->getSubItemSize() : 0;
 		for (int i = 0; i < ecidSize; i++) {
-			EQConstant eqc;
-			unsigned short id;
-			if (pItem->getSubItemU2(i, id)) {
+			EQConstant eqc{};
+			unsigned short id = 0;
+			if (pItem && pItem->getSubItemU2(i, id)) {
 				eqc.id = id;
 				eqcs.push_back(eqc);
 			}
 		}
 	}
-
-
-	// 浜ょ敱涓婂眰搴旂敤鏉ヨ幏鍙栨満鍣ㄥ父閲忓��
-	if (m_listener.onEQConstantRequest != nullptr) {
-		m_listener.onEQConstantRequest(this, eqcs);
+	// 绌哄垪琛ㄨ〃绀鸿姹傚叏閮� ECID
+	if (eqcs.empty()) {
+		for (const auto& e : m_equipmentConstants) {
+			EQConstant eqc{};
+			eqc.id = e.id;
+			strcpy_s(eqc.szValue, EQCONSTANT_VALUE_MAX, e.value.c_str());
+			eqcs.push_back(eqc);
+		}
+	} else {
+		for (auto& item : eqcs) {
+			auto it = std::find_if(m_equipmentConstants.begin(), m_equipmentConstants.end(),
+				[&](const EquipmentConstantEntry& e) { return e.id == item.id; });
+			if (it != m_equipmentConstants.end()) {
+				strcpy_s(item.szValue, EQCONSTANT_VALUE_MAX, it->value.c_str());
+			} else {
+				item.szValue[0] = '\0'; // unknown -> empty
+			}
+		}
 	}
-
 
 	// 鍥炲
 	IMessage* pMessage = NULL;
-	HSMS_Create1Message(pMessage, m_nSessionId, 1, 14, pRecv->getHeader()->systemBytes);
+	HSMS_Create1Message(pMessage, m_nSessionId, 2, 14, pRecv->getHeader()->systemBytes);
 	ASSERT(pMessage);
 	ISECS2Item* pItem = pMessage->getBody();
 	for (auto& item : eqcs) {
@@ -1037,7 +2649,9 @@
 	}
 
 	m_pPassive->sendMessage(pMessage);
-	LOGI("<HSMS>[SECS Msg SEND]S2F14 (SysByte=%u)", pMessage->getHeader()->systemBytes);
+	LOGI("<HSMS>[SEND]sessionId:%d, sType:%d systemBytes:%d",
+		pMessage->getHeader()->sessionId, pMessage->getHeader()->sType, pMessage->getHeader()->systemBytes);
+	LogSecsMessageBrief("<HSMS>[SEND]", pMessage);
 	HSMS_Destroy1Message(pMessage);
 
 	return 0;
@@ -1051,33 +2665,38 @@
 	}
 
 
-	// 瑕佽缃殑甯搁噺琛ㄨ〃
-	BOOL bCheckData = FALSE;
+	// 瑕佽缃殑甯搁噺琛�
 	std::vector<EQConstant> eqcs;
 	{
 		ISECS2Item* pItem = pRecv->getBody();
-		int ecidSize = pItem->getSubItemSize();
+		int ecidSize = pItem ? pItem->getSubItemSize() : 0;
 		for (int i = 0; i < ecidSize; i++) {
-			ISECS2Item* pItemEqc = pItem->getSubItem(i);
-			if (pItemEqc != nullptr) {
-				EQConstant eqc;
-				unsigned short eqcid;
-				const char* pszValue;
-				if (pItemEqc->getSubItemU2(0, eqcid)
-					&& pItemEqc->getSubItemString(1, pszValue)) {
-					eqc.id = eqcid;
-					strcpy_s(eqc.szValue, EQCONSTANT_VALUE_MAX, pszValue);
-					eqcs.push_back(eqc);
-				}
+			ISECS2Item* pItemEqc = pItem ? pItem->getSubItem(i) : nullptr;
+			if (pItemEqc == nullptr) continue;
+			EQConstant eqc{};
+			unsigned short eqcid = 0;
+			const char* pszValue = nullptr;
+			if (pItemEqc->getSubItemU2(0, eqcid)
+				&& pItemEqc->getSubItemString(1, pszValue)) {
+				eqc.id = eqcid;
+				strcpy_s(eqc.szValue, EQCONSTANT_VALUE_MAX, pszValue);
+				eqcs.push_back(eqc);
 			}
 		}
 	}
 
-
-	// 浜ょ敱涓婂眰搴旂敤鏉ヤ繚瀛樺拰璁剧疆鏈哄櫒甯搁噺鍊�
-	std::vector<unsigned int> ecvs;
-	if (m_listener.onEQConstantSend != nullptr) {
-		m_listener.onEQConstantSend(this, eqcs);
+	// 鏇存柊鍐呭瓨琛ㄥ苟钀界洏
+	bool changed = false;
+	for (auto& item : eqcs) {
+		auto it = std::find_if(m_equipmentConstants.begin(), m_equipmentConstants.end(),
+			[&](const EquipmentConstantEntry& e) { return e.id == item.id; });
+		if (it != m_equipmentConstants.end()) {
+			it->value = item.szValue;
+			changed = true;
+		}
+	}
+	if (changed && !m_strEquipmentConstantFilepath.empty()) {
+		writeEquipmentConstantsToFile(m_strEquipmentConstantFilepath);
 	}
 
 
@@ -1133,13 +2752,14 @@
 
 	ISECS2Item* pBody = pRecv->getBody();
 	ISECS2Item* defineItem, *rptListItem, * vidListItem;
-	unsigned int dataId, rptid, vid;
+	unsigned short dataId;
+	unsigned int rptid, vid;
 
-	if (!pBody->getSubItemU4(0, dataId)) goto MYREPLY;
+	if (!pBody->getSubItemU2(0, dataId)) goto MYREPLY;
 	rptListItem = pBody->getSubItem(1);
 	if (rptListItem == nullptr) goto MYREPLY;
 	if (rptListItem->getSubItemSize() == 0) {
-		clearAllReport();
+		clearAllReport(TRUE);
 		goto MYREPLY;
 	}
 
@@ -1159,8 +2779,10 @@
 			}
 		}
 
-		removeReport(rptid);
-		if (!vids.empty()) {
+		if (vids.empty()) {
+			deleteReport(rptid);
+		} else {
+			removeReport(rptid);
 			pReport = defineReport(rptid, vids);
 		}
 
@@ -1189,8 +2811,10 @@
 
 	ISECS2Item* pBody = pRecv->getBody();
 	ISECS2Item* linkItem, *ceidListItem, *rptListItem;
-	unsigned int dataId, ceid, rptid;
-	if (!pBody->getSubItemU4(0, dataId)) goto MYREPLY;
+	unsigned short dataId;
+	unsigned int ceid, rptid;
+	bool bChanged = false;
+	if (!pBody->getSubItemU2(0, dataId)) goto MYREPLY;
 	ceidListItem = pBody->getSubItem(1);
 	if (ceidListItem == nullptr) goto MYREPLY;
 	for (int i = 0; i < ceidListItem->getSubItemSize(); i++) {
@@ -1202,21 +2826,31 @@
 			int prtCount = rptListItem->getSubItemSize();
 			if (prtCount == 0) {
 				unlinkEventReport(ceid);
+				bChanged = true;
 			}
 			else {
 				for (int k = 0; k < prtCount; k++) {
 					if (rptListItem->getSubItemU4(k, rptid)) {
 						linkEventReport(ceid, rptid);
+						bChanged = true;
 					}
 				}
 			}
 		}
 	}
 
+	// 鎸佷箙鍖栧埌 CollectionEventList.txt锛堜究浜庝笅娆″惎鍔ㄤ粛淇濇寔 Link/Unlink 缁撴灉锛�
+	if (bChanged && !m_strCollectionEventFilepath.empty()) {
+		writeCollectionEventsToFile(m_strCollectionEventFilepath);
+	}
 
 	// 妫�楠岀粨鏋滄槸鍚︽纭�
 	for (auto item : m_collectionEvents) {
-		LOGE("=== ceid:%d, prtid:%d", item->getEventId(), item->getFirstPortID());
+		unsigned int reportId = item->getFirstReportID();
+		if(reportId != 0)
+			LOGI("=== ceid:%d, prtid:%d", item->getEventId(), reportId);
+		else 
+			LOGI("=== ceid:%d, prtid:--", item->getEventId());
 	}
 	
 MYREPLY:
@@ -1324,9 +2958,12 @@
 
 	// 娓呯┖鎵�鏈�
 	if (pBody->getSubItemSize() == 0) {
-		m_spoolingConfig.clear();
+		LOGI("<CHsmsPassive>turns off all streams and fns");
+		m_spoolBlacklistByStream.clear();
+		m_spoolingEnabled = false;
 		goto MYREPLY;
 	}
+	m_spoolingEnabled = true;
 
 	// 渚濇閰嶇疆Stream
 	for (int i = 0; i < pBody->getSubItemSize(); i++) {
@@ -1335,26 +2972,34 @@
 		unsigned char STRID, FCNID;
 		pStreamItem->getSubItemU1(0, STRID);
 		ISECS2Item* pFcnItemList = pStreamItem->getSubItem(1);
-		if (pFcnItemList->getSubItemSize() == 0) {
-			m_spoolingConfig[STRID].clear();
+		if (pFcnItemList == nullptr || pFcnItemList->getSubItemSize() == 0) {
+			// No functions listed => blacklist the whole stream
+			m_spoolBlacklistByStream[STRID].clear();
 		}
 		else {
+			// Update blacklist for this stream
+			m_spoolBlacklistByStream[STRID].clear();
 			for (int j = 0; j < pFcnItemList->getSubItemSize(); j++) {
 				pFcnItemList->getSubItemU1(j, FCNID);
-				m_spoolingConfig[STRID].insert(FCNID);
+				m_spoolBlacklistByStream[STRID].insert(FCNID);
 			}
 		}
 	}
 
 	// 鎵撳嵃楠岃瘉缁撴灉
-	for (auto s : m_spoolingConfig) {
-		LOGI("====> stream:%d", s.first);
-		for (auto f : s.second) {
-			LOGI("function:%d", f);
+	for (auto s : m_spoolBlacklistByStream) {
+		LOGI("====> spool blacklist stream:%d", s.first);
+		if (s.second.empty()) {
+			LOGI("blacklist all functions");
+		}
+		else {
+			for (auto f : s.second) {
+				LOGI("blacklist function:%d", f);
+			}
 		}
 	}
 MYREPLY:
-	replyAck(2, 42, pRecv->getHeader()->systemBytes, BYTE(0), "ERACK");
+	replyAck(2, 44, pRecv->getHeader()->systemBytes, BYTE(0), "ERACK");
 	return 0;
 }
 
@@ -1375,14 +3020,21 @@
 		goto MYREPLY;
 	}
 
+	if (!isHostCommandAllowed()) {
+		CAACK = CAACK_5;
+		ERRCODE = CAACK_5;
+		strError = "rejected - ControlState not OnlineRemote";
+		goto MYREPLY;
+	}
+
 
 	ISECS2Item* pBody = pRecv->getBody();
 	if (pBody == nullptr || pBody->getType() != SITYPE::L) ER_PARAM_ERROR;
 
-	unsigned int DATAID;
+	unsigned short DATAID;
 	unsigned char PTN;
 	const char* pszCarrierAction, *pszCarrierId;
-	pBody->getSubItemU4(0, DATAID);
+	pBody->getSubItemU2(0, DATAID);
 	pBody->getSubItemString(1, pszCarrierAction);
 	pBody->getSubItemString(2, pszCarrierId);
 	pBody->getSubItemU1(3, PTN);
@@ -1403,7 +3055,9 @@
 	pErrItem->addU4Item(ERRCODE, "ERRCODE");
 	pErrItem->addItem(strError.c_str(), "ERRTEXT");
 	m_pPassive->sendMessage(pMessage);
-	LOGI("<HSMS>[SECS Msg SEND]S3F18 (SysByte=%u)", pMessage->getHeader()->systemBytes);
+	LOGI("<HSMS>[SEND]sessionId:%d, sType:%d systemBytes:%d",
+		pMessage->getHeader()->sessionId, pMessage->getHeader()->sType, pMessage->getHeader()->systemBytes);
+	LogSecsMessageBrief("<HSMS>[SEND]", pMessage);
 	HSMS_Destroy1Message(pMessage);
 	
 	return 0;
@@ -1463,6 +3117,7 @@
 
 	// 涓㈠純
 	if (RSDC == 1) {
+		LOGI("<CHsmsPassive>Purge Spooled Data.");
 		Lock();
 		for (auto item : m_listActionSpooling) {
 			delete item;
@@ -1471,6 +3126,7 @@
 		Unlock();
 	}
 	else {
+		LOGI("<CHsmsPassive>Request Spooled Data.");
 		Lock();
 		for (auto item : m_listActionSpooling) {
 			m_listAction.push_back(item);
@@ -1480,6 +3136,86 @@
 		SetEvent(m_hCimWorkEvent);
 	}
 
+	return 0;
+}
+
+// S7F17 Delete Process Program (PPID list) / S7F18
+int CHsmsPassive::replyDeletePPID(IMessage* pRecv)
+{
+	if (m_pPassive == NULL || STATE::SELECTED != m_pPassive->getState()) {
+		return ER_NOTSELECT;
+	}
+
+	bool allOk = true;
+	const bool deleteAll = (pRecv->getBody() == nullptr || pRecv->getBody()->getSubItemSize() == 0);
+	std::vector<std::string> ppids;
+	ISECS2Item* pBody = pRecv->getBody();
+	const int nCount = pBody ? pBody->getSubItemSize() : 0;
+	for (int i = 0; i < nCount; ++i) {
+		const char* pszPPID = nullptr;
+		if (pBody->getSubItemString(i, pszPPID) && pszPPID != nullptr) {
+			ppids.emplace_back(pszPPID);
+		}
+		else {
+			allOk = false;
+		}
+	}
+
+	if (deleteAll || !ppids.empty()) {
+		if (m_listener.onDeletePPID != nullptr) {
+			allOk = m_listener.onDeletePPID(this, ppids);
+		}
+		else {
+			// no handler provided; treat as failure
+			allOk = false;
+			LOGW("<HSMS>DeletePPID request ignored: no onDeletePPID listener");
+		}
+	}
+
+
+	replyAck(7, 18, pRecv->getHeader()->systemBytes, allOk ? BYTE(0) : BYTE(1), "ACKC7");
+	return 0;
+}
+
+// S7F5 Process Program Request -> reply S7F6 with PPID + PPBODY
+int CHsmsPassive::replyProcessProgramRequest(IMessage* pRecv)
+{
+	if (m_pPassive == NULL || STATE::SELECTED != m_pPassive->getState()) {
+		return ER_NOTSELECT;
+	}
+	ISECS2Item* pBody = pRecv->getBody();
+	const char* pszPPID = nullptr;
+	if (pBody == nullptr || !pBody->getString(pszPPID) || pszPPID == nullptr) {
+		return ER_PARAM_ERROR;
+	}
+	std::string ppid(pszPPID);
+	std::string ppbody;
+	// 绠�鍗曡仛鍚堬細浠� RecipeManager 鍙栬澶囬厤鏂癸紝鎷兼垚鏂囨湰
+	auto recipeInfo = RecipeManager::getInstance().getRecipeByPPID(ppid);
+	if (!recipeInfo.strPPID.empty()) {
+		for (const auto& dev : recipeInfo.vecDeviceList) {
+			if (!ppbody.empty()) ppbody.append("\n");
+			ppbody.append(dev.strDeviceName);
+			ppbody.append(",");
+			ppbody.append(std::to_string(dev.nRecipeID));
+			ppbody.append(",");
+			ppbody.append(dev.strRecipeName);
+			// 闄勫姞鍙傛暟澶у皬淇℃伅锛堢洰鍓嶇己灏戝叿浣撳弬鏁板垪琛級
+			ppbody.append(",paramsSize=");
+			ppbody.append(std::to_string(dev.paramsRawData.size()));
+		}
+	}
+
+	IMessage* pMessage = nullptr;
+	if (HSMS_Create1Message(pMessage, m_nSessionId, 7, 6, pRecv->getHeader()->systemBytes) != 0 || pMessage == nullptr) {
+		return ER_CREATED_MESSAGE;
+	}
+	ISECS2Item* pRspBody = pMessage->getBody(); // top-level L:2
+	pRspBody->addItem(ppid.c_str(), "PPID");
+	pRspBody->addItem(ppbody.c_str(), "PPBODY");
+	m_pPassive->sendMessage(pMessage);
+	LogSecsMessageBrief("<HSMS>[SEND]", pMessage);
+	HSMS_Destroy1Message(pMessage);
 	return 0;
 }
 
@@ -1504,7 +3240,9 @@
 	}
 
 	m_pPassive->sendMessage(pMessage);
-	LOGI("<HSMS>[SECS Msg SEND]S7F20 (SysByte=%u)", pMessage->getHeader()->systemBytes);
+	LOGI("<HSMS>[SEND]sessionId:%d, sType:%d systemBytes:%d",
+		pMessage->getHeader()->sessionId, pMessage->getHeader()->sType, pMessage->getHeader()->systemBytes);
+	LogSecsMessageBrief("<HSMS>[SEND]", pMessage);
 	HSMS_Destroy1Message(pMessage);
 
 	return 0;
@@ -1571,6 +3309,13 @@
 	ISECS2Item* pReplyItemAcks = pReply->getBody()->addItem();
 	ISECS2Item* pReplyItemAck = pReplyItemAcks->addU1Item(0, "OBJACK");
 	ISECS2Item* pReplyItemErrs = pReplyItemAcks->addItem();
+
+	if (!isHostCommandAllowed()) {
+		ISECS2Item* pItemError = pReplyItemErrs->addItem();
+		pItemError->addU4Item(2001, "ERRCODE");
+		pItemError->addItem("rejected - ControlState not OnlineRemote", "ERRTEXT");
+		goto MYREPLY;
+	}
 
 	// 褰撳墠鍙鐞嗙被鍚勪负ControlJob
 	if (_strcmpi(pszObjType, "ControlJob") == 0) {
@@ -1668,7 +3413,9 @@
 MYREPLY:
 	pReplyItemAck->setU1(bCreateOk ? 0 : 1, "OBJACK");
 	m_pPassive->sendMessage(pReply);
-	LOGI("<HSMS>[SECS Msg SEND]S14F10 (SysByte=%u)", pReply->getHeader()->systemBytes);
+	LOGI("<HSMS>[SEND]sessionId:%d, sType:%d systemBytes:%d",
+		pReply->getHeader()->sessionId, pReply->getHeader()->sType, pReply->getHeader()->systemBytes);
+	LogSecsMessageBrief("<HSMS>[SEND]", pReply);
 	HSMS_Destroy1Message(pReply);
 
 
@@ -1684,10 +3431,49 @@
 	ISECS2Item* pBody = pRecv->getBody();
 	if (pBody == nullptr || pBody->getType() != SITYPE::L) ER_PARAM_ERROR;
 
+	if (!isHostCommandAllowed()) {
+		IMessage* pMessage = NULL;
+		HSMS_Create1Message(pMessage, m_nSessionId, 16, 16, ++m_nSystemByte);
+		ASSERT(pMessage);
+		ISECS2Item* pItemPrjobIds = pMessage->getBody()->addItem();
+		ISECS2Item* pItemErrors = pMessage->getBody()->addItem();
+		pItemErrors->addBoolItem(false, "ACKA");
+		ISECS2Item* pItemErrors2 = pItemErrors->addItem();
+		auto err = pItemErrors2->addItem();
+		err->addU4Item(2001, "ERRCODE");
+		err->addItem("rejected - ControlState not OnlineRemote", "ERRTEXT");
+		m_pPassive->sendMessage(pMessage);
+		LOGI("<HSMS>[SEND]sessionId:%d, sType:%d systemBytes:%d",
+			pMessage->getHeader()->sessionId, pMessage->getHeader()->sType, pMessage->getHeader()->systemBytes);
+		LogSecsMessageBrief("<HSMS>[SEND]", pMessage);
+		HSMS_Destroy1Message(pMessage);
+		return 0;
+	}
+
 
 	// 瑙i噴鏁版嵁锛屽緱鍒癈ProcessJob
+	// 瀹归噺鍓嶇疆妫�鏌ワ細褰撳墠瀹炵幇浠呮敮鎸佸崟鎵� PJ 闆嗗悎锛屽鏋滃凡鏈� PJ/CJ锛岀洿鎺ヨ繑鍥� ACKA=false
+	if (m_pModel != nullptr && !m_pModel->getMaster().isProcessJobsEmpty()) {
+		IMessage* pMessage = NULL;
+		HSMS_Create1Message(pMessage, m_nSessionId, 16, 16, ++m_nSystemByte);
+		ASSERT(pMessage);
+		pMessage->getBody()->addItem(); // PRJOBID list 涓虹┖
+		ISECS2Item* pItemErrors = pMessage->getBody()->addItem();
+		pItemErrors->addBoolItem(false, "ACKA");
+		ISECS2Item* pItemErrors2 = pItemErrors->addItem();
+		auto err = pItemErrors2->addItem();
+		err->addU4Item(1000, "ERRCODE");
+		err->addItem("PJobSpace=0 (existing ProcessJob/ControlJob)", "ERRTEXT");
+		m_pPassive->sendMessage(pMessage);
+		LOGI("<HSMS>[SEND]sessionId:%d, sType:%d systemBytes:%d",
+			pMessage->getHeader()->sessionId, pMessage->getHeader()->sType, pMessage->getHeader()->systemBytes);
+		LogSecsMessageBrief("<HSMS>[SEND]", pMessage);
+		HSMS_Destroy1Message(pMessage);
+		return 0;
+	}
+
 	ISECS2Item* pItemPjs, * pItemPj,* pItemCarriers, * pItemCarrier, *pItemSlots, *pItemRecipes;
-	unsigned int DATAID;
+	unsigned short DATAID;
 	const char* pszPrjobid, *pszMF, *pszCarrierId, *pszRecipeName;
 	std::string strCarrierId;
 	unsigned int len;
@@ -1695,7 +3481,7 @@
 	std::vector<unsigned char> slots;
 	std::vector<SERVO::CProcessJob*> pjs;
 
-	if (!pBody->getSubItemU4(0, DATAID)) return ER_PARAM_ERROR;
+	if (!pBody->getSubItemU2(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++) {
@@ -1736,11 +3522,12 @@
 		pjs.push_back(pj);
 	}
 
+
 	ASSERT(m_listener.onPRJobMultiCreate != nullptr);
 	int nRet = m_listener.onPRJobMultiCreate(this, pjs);
 
 
-	// 鍥炲鎶ユ枃
+	// 鍥炲鎶ユ枃锛堝湪鏍¢獙/钀藉簱鍚庡啀鍥炲锛屼互渚垮甫涓婄湡瀹炵殑 issues锛�
 	IMessage* pMessage = NULL;
 	HSMS_Create1Message(pMessage, m_nSessionId, 16, 16, ++m_nSystemByte);
 	ASSERT(pMessage);
@@ -1766,8 +3553,14 @@
 			}
 		}
 	}
+	else {
+		pItemErrors->addBoolItem(true, "ACKA");
+		pItemErrors->addItem(); // 绌哄垪琛�
+	}
 	m_pPassive->sendMessage(pMessage);
-	LOGI("<HSMS>[SECS Msg SEND]S16F16 (SysByte=%u)", pMessage->getHeader()->systemBytes);
+	LOGI("<HSMS>[SEND]sessionId:%d, sType:%d systemBytes:%d",
+		pMessage->getHeader()->sessionId, pMessage->getHeader()->sType, pMessage->getHeader()->systemBytes);
+	LogSecsMessageBrief("<HSMS>[SEND]", pMessage);
 	HSMS_Destroy1Message(pMessage);
 
 
@@ -1790,15 +3583,25 @@
 	CHsmsAction* pAction = new CHsmsAction(ACTION_ALARM_REPORT, TRUE, m_nActionTimeout);
 
 	IMessage* pMessage = NULL;
-	HSMS_Create1Message(pMessage, m_nSessionId, 5 | REPLY, 1, ++m_nSystemByte);
-	ASSERT(pMessage);
+	if (HSMS_Create1Message(pMessage, m_nSessionId, 5 | REPLY, 1, ++m_nSystemByte) != 0 || pMessage == NULL) {
+		LOGE("<HSMS>S5F1 create message failed");
+		delete pAction;
+		Unlock();
+		return ER_CREATED_MESSAGE;
+	}
 	ISECS2Item* pItem = pMessage->getBody();
 	pItem->addBinaryItem(szALCD, 1, "ALCD");
 	pItem->addU4Item(ALID, "ALID");
 	pItem->addItem(ALTX, "ALTX");
 	pAction->setSendMessage(pMessage);
 	if (m_pPassive == NULL || STATE::SELECTED != m_pPassive->getState()) {
-		m_listActionSpooling.push_back(pAction);
+		if (shouldSpool(5, 1)) {
+			m_listActionSpooling.push_back(pAction);
+		}
+		else {
+			LOGI("<HSMS>spooling disabled for S5F1, drop alarm report");
+			delete pAction;
+		}
 	}
 	else {
 		m_listAction.push_back(pAction);
@@ -1810,40 +3613,62 @@
 }
 
 // S6F11
-static unsigned int DATAID = 1;
+static unsigned short DATAID = 0;
 int CHsmsPassive::requestEventReportSend(unsigned int CEID)
 {
 	SERVO::CCollectionEvent* pEvent = getEvent(CEID);
 	if (pEvent == nullptr) {
 		return ER_NO_EVENT;
 	}
+	// 瑙﹀彂 PauseEvent 妫�娴嬫々锛堢敱 Master 璐熻矗瀹為檯绛栫暐锛�
+	if (m_pModel != nullptr) {
+		m_pModel->getMaster().handleCollectionEvent(CEID);
+	}
 
 	SERVO::CReport* pReport = pEvent->getFirstReport();
-	if (pReport == nullptr) {
-		return ER_UNLINK_EVENT_REPORT;
-	}
 
 
 	Lock();
 	CHsmsAction* pAction = new CHsmsAction(ACTION_EVENT_REPORT, TRUE, m_nActionTimeout);
 	IMessage* pMessage = NULL;
-	HSMS_Create1Message(pMessage, m_nSessionId, 6 | REPLY, 11, ++m_nSystemByte);
-	ASSERT(pMessage);
+	if (HSMS_Create1Message(pMessage, m_nSessionId, 6 | REPLY, 11, ++m_nSystemByte) != 0 || pMessage == NULL) {
+		LOGE("<HSMS>S6F11 create message failed");
+		delete pAction;
+		Unlock();
+		return ER_CREATED_MESSAGE;
+	}
 	ISECS2Item* pItem = pMessage->getBody();
-	pItem->addU4Item(++DATAID, "DATAID");
-	pItem->addU4Item(CEID, "CEID");
-	ISECS2Item* pItemList1 = pItem->addItem();
-	ISECS2Item* pItemList2 = pItemList1->addItem();
-	pItemList2->addU4Item(pReport->getReportId(), "RPTID");
-	ISECS2Item* pItemList3 = pItemList2->addItem();
+	// pItem->addU2Item(++DATAID, "DATAID");		// 鏍规嵁鍒殑鏃ュ織鏄剧ずDATAID鎭掍负0锛屾墍浠ユ垜浠厛鐓т娇鐢�0
+	pItem->addU2Item(0, "DATAID");
+	std::string ceidNote("CEID");
+	if (pEvent != nullptr) {
+		auto& name = pEvent->getName();
+		if (!name.empty()) {
+			ceidNote += " -> ";
+			ceidNote += name;
+		}
+	}
+	pItem->addU4Item(CEID, ceidNote.c_str());
+	ISECS2Item* pItemList1 = pItem->addItem(); // L[n] reports
+	if (pReport != nullptr) {
+		ISECS2Item* pItemList2 = pItemList1->addItem();
+		pItemList2->addU4Item(pReport->getReportId(), "RPTID");
+		ISECS2Item* pItemList3 = pItemList2->addItem();
 
-	auto vars = pReport->getVariables();
-	for (auto var : vars) {
-		addVariableValueToItem(pItemList3, var);
+		auto vars = pReport->getVariables();
+		for (auto var : vars) {
+			addVariableValueToItem(pItemList3, var);
+		}
 	}
 	pAction->setSendMessage(pMessage);
 	if (m_pPassive == NULL || STATE::SELECTED != m_pPassive->getState()) {
-		m_listActionSpooling.push_back(pAction);
+		if (shouldSpool(6, 11)) {
+			m_listActionSpooling.push_back(pAction);
+		}
+		else {
+			LOGI("<HSMS>spooling disabled for S6F11, drop event report (CEID=%u)", CEID);
+			delete pAction;
+		}
 	}
 	else {
 		m_listAction.push_back(pAction);
@@ -1874,6 +3699,21 @@
 	return requestEventReportSend("CarrierID_Readed");
 }
 
+int CHsmsPassive::requestEventReportSend_CheckSlotMap()
+{
+	return requestEventReportSend("CheckSlotMap");
+}
+
+int CHsmsPassive::requestEventReportSend_SlotMapVerificationOK()
+{
+	return requestEventReportSend("SlotMapVerificationOK");
+}
+
+int CHsmsPassive::requestEventReportSend_SlotMapVerificationNG()
+{
+	return requestEventReportSend("SlotMapVerificationNG");
+}
+
 int CHsmsPassive::requestEventReportSend_Port_Unload_Ready()
 {
 	return requestEventReportSend("Port_Unload_Ready");
@@ -1882,6 +3722,11 @@
 int CHsmsPassive::requestEventReportSend_Port_Load_Ready()
 {
 	return requestEventReportSend("Port_Load_Ready");
+}
+
+int CHsmsPassive::requestEventReportSend_Port_Ready_To_Release()
+{
+	return requestEventReportSend("Port_Ready_To_Release");
 }
 
 int CHsmsPassive::requestEventReportSend_Port_Blocked()
@@ -1924,5 +3769,54 @@
 	return requestEventReportSend("Panel_End");
 }
 
+int CHsmsPassive::requestEventReportSend_OCR_PanelID_Read_OK()
+{
+	return requestEventReportSend_OCR_PanelID_Read(1);
+}
+
+int CHsmsPassive::requestEventReportSend_OCR_PanelID_Read(short vcrResult)
+{
+	const char* eventName = "OCR_PanelID_Read_OK";
+	switch (vcrResult) {
+	case 1: // OK & Match
+		eventName = "OCR_PanelID_Read_OK";
+		break;
+	case 2: // OK & Mismatch
+		eventName = "OCR_PanelID_Read_Mismatch";
+		break;
+	case 3: // Fail & KeyIn Match
+		eventName = "OCR_PanelID_Read_NG";
+		break;
+	case 4: // Fail & KeyIn Mismatch
+		eventName = "OCR_PanelID_Read_NG_Mismatch";
+		break;
+	default:
+		LOGE("<CHsmsPassive>Unknown VCR result=%d, fallback to OCR_PanelID_Read_OK", vcrResult);
+		eventName = "OCR_PanelID_Read_OK";
+		break;
+	}
+
+	return requestEventReportSend(eventName);
+}
+
+int CHsmsPassive::requestEventReportSend_LoadPortNotAssoc()
+{
+	return requestEventReportSend("LoadPortNotAssoc");
+}
+
+int CHsmsPassive::requestEventReportSend_ProcessDataReport()
+{
+	return requestEventReportSend("ProcessDataReport");
+}
+
+int CHsmsPassive::requestEventReportSend_SubEqpStart()
+{
+	return requestEventReportSend("SubEqpStart");
+}
+
+int CHsmsPassive::requestEventReportSend_SubEqpEnd()
+{
+	return requestEventReportSend("SubEqpEnd");
+}
 
 
diff --git a/SourceCode/Bond/Servo/HsmsPassive.h b/SourceCode/Bond/Servo/HsmsPassive.h
index 1863abd..81c5188 100644
--- a/SourceCode/Bond/Servo/HsmsPassive.h
+++ b/SourceCode/Bond/Servo/HsmsPassive.h
@@ -1,4 +1,4 @@
-#pragma once
+锘�#pragma once
 #include <string>
 #include <list>
 #include "HsmsAction.h"
@@ -9,6 +9,7 @@
 #include "CCollectionEvent.h"
 #include "ProcessJob.h"
 #include "CControlJob.h"
+#include "CDataVariable.h"
 
 
 #define EQCONSTANT_VALUE_MAX	64
@@ -26,6 +27,8 @@
 #define ER_UNLINK_EVENT_REPORT	-5
 #define ER_NO_PPID_LIST			-6
 #define ER_NOT_SUPPORTED		-7
+#define ER_CREATED_MESSAGE		-8
+
 
 
 /* CAACK */
@@ -39,7 +42,7 @@
 #define CAACK_6					6		/* command performed with errors */
 
 /*
- * 常量数据结构
+ * 甯搁噺鏁版嵁缁撴瀯
  */
 typedef struct _EQConstant
 {
@@ -48,7 +51,7 @@
 } EQConstant;
 
 /*
- * Command 数据结构
+ * Command 鏁版嵁缁撴瀯
  */
 typedef struct _CommandParameter
 {
@@ -57,7 +60,7 @@
 } CommandParameter;
 
 /*
- * Report 数据结构
+ * Report 鏁版嵁缁撴瀯
  */
 typedef struct _REPORT
 {
@@ -66,7 +69,7 @@
 } REPORT;
 
 /*
- * Value 数据结构
+ * Value 鏁版嵁缁撴瀯
  */
 typedef struct _VALUE
 {
@@ -76,7 +79,6 @@
 
 
 typedef std::function<void(void* pFrom)> SECSEQOFFLINE;
-typedef std::function<void(void* pFrom, std::vector<EQConstant>&)> SECSEQCONSTANTREQUEST;
 typedef std::function<void(void* pFrom, const char*, std::vector<CommandParameter>&)> SECSCommand;
 typedef std::function<void(void* pFrom, SYSTEMTIME& time)> DATETIMESYNC;
 typedef std::function<void(void* pFrom, bool bEnable, std::vector<unsigned int>& ids)> EDEVENTREPORT;
@@ -90,17 +92,17 @@
 	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 std::function<bool(void* pFrom, const std::vector<std::string>& ppids)> DELETEPPID;
 typedef struct _SECSListener
 {
 	SECSEQOFFLINE				onEQOffLine;
 	SECSEQOFFLINE				onEQOnLine;
-	SECSEQCONSTANTREQUEST		onEQConstantRequest;
-	SECSEQCONSTANTREQUEST		onEQConstantSend;
 	SECSCommand					onCommand;
 	DATETIMESYNC				onDatetimeSync;
 	EDEVENTREPORT				onEnableDisableEventReport;
 	EDALARMREPORT				onEnableDisableAlarmReport;
 	QUERYPPIDLIST				onQueryPPIDList;
+	DELETEPPID					onDeletePPID;
 	CARRIERACTION				onCarrierAction;
 	PRJOBMULTICREATE			onPRJobMultiCreate;
 	CONTROLJOBCREATE			onControlJobCreate;
@@ -115,62 +117,89 @@
 	~CHsmsPassive();
 
 public:
-	/* 设置机器型号 最大长度 20 bytes */
+	/* 璁剧疆鏈哄櫒鍨嬪彿 鏈�澶ч暱搴� 20 bytes */
 	void setEquipmentModelType(const char* pszMode);
 
-	/* 设置软件版本号 最大长度 20 bytes */
+	/* 璁剧疆杞欢鐗堟湰鍙� 鏈�澶ч暱搴� 20 bytes */
 	void setSoftRev(const char* pszRev);
 
-	/* 添加变量值到ISECS2Item */
+	/* 娣诲姞鍙橀噺鍊煎埌ISECS2Item */
 	void addVariableValueToItem(ISECS2Item* pParent, SERVO::CVariable* pVariable);
 
-	// 连接Report
+	// 杩炴帴Report
 	void linkEventReport(unsigned int CEID, unsigned int RPTID);
 
-	// 取消连接report
+	// 鍙栨秷杩炴帴report
 	void unlinkEventReport(unsigned int CEID);
 
 	// define Report
 	SERVO::CReport* defineReport(unsigned int RPTID, std::vector<unsigned int>& vids);
 
-	// 取消 define report
+	// 鍙栨秷 define report
 	bool removeReport(int rptid);
-	void clearAllReport();
+	int deleteReport(int rptid);
+	int addReport(int rptid, const std::vector<unsigned int>& vids);
+	int updateReport(int rptid, const std::vector<unsigned int>& vids);
+	void clearAllReport(BOOL bSave = FALSE);
 
-	// 从文件中加载CVariable列表
+	// 浠庢枃浠朵腑鍔犺浇CVariable鍒楄〃
 	int loadVarialbles(const char* pszFilepath);
+	// 浠庢枃浠朵腑鍔犺浇CDataVariable鍒楄〃
+	int loadDataVarialbles(const char* pszFilepath);
+	// 浠庢枃浠朵腑鍔犺浇Equipment Constant鍒楄〃
+	int loadEquipmentConstants(const char* pszFilepath);
 
-	// 取得CVariable列表
+	// 鍙栧緱CVariable鍒楄〃
 	std::vector<SERVO::CVariable*>& getVariables();
+	unsigned int getMaxVariableId() const;
+	std::vector<SERVO::CDataVariable*>& getDataVariables();
+	unsigned int getMaxDataVariableId() const;
 
-	// 取得指定Variable
+	// 鍙栧緱鎸囧畾Variable
 	SERVO::CVariable* getVariable(int variableId);
 	SERVO::CVariable* getVariable(const char* pszName);
+	SERVO::CDataVariable* getDataVariable(int dvid);
+	SERVO::CDataVariable* getDataVariable(const char* pszName);
+	int getCurrentControlState();
+	bool isHostCommandAllowed();
+	int deleteVariable(int variableId);
+	int addVariable(const char* pszName, const char* pszFormat, const char* pszRemark, int& outId);
+	int updateVariable(int variableId, const char* pszName, const char* pszFormat, const char* pszRemark);
+	int deleteDataVariable(int dvid);
+	int addDataVariable(const char* pszName, const char* pszFormat, const char* pszRemark, int& outId);
+	int updateDataVariable(int dvid, const char* pszName, const char* pszFormat, const char* pszRemark);
 
-	// 设置变量值
+	// 璁剧疆鍙橀噺鍊�
 	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);
+	// 鎵ц涓�娈垫寔閿佺殑浠g爜鍧楋紝鐢ㄤ簬淇濊瘉 set+send 鐨勫師瀛愭��
+	void withVariableLock(const std::function<void()>& fn);
 
-	// 从文件中加载CReport列表
+	// 浠庢枃浠朵腑鍔犺浇CReport鍒楄〃
 	int loadReports(const char* pszFilepath);
 
-	// 取得Report列表
+	// 鍙栧緱Report鍒楄〃
 	std::vector<SERVO::CReport*>& getReports();
+	unsigned int getMaxReportId() const;
 
-	// 从文件中加载CCollectionEvent列表
+	// 浠庢枃浠朵腑鍔犺浇CCollectionEvent鍒楄〃
 	int loadCollectionEvents(const char* pszFilepath);
 
-	// 取得CCollectionEvent列表
+	// 鍙栧緱CCollectionEvent鍒楄〃
 	std::vector<SERVO::CCollectionEvent*>& getCollectionEvents();
+	unsigned int getMaxCollectionEventId() const;
 
-	// 取消/删除所有CollectionEvent
+	// 鍙栨秷/鍒犻櫎鎵�鏈塁ollectionEvent
 	void clearAllCollectionEvent();
+	int deleteCollectionEvent(unsigned short CEID);
+	int addCollectionEvent(unsigned int CEID, const char* name, const char* desc, const std::vector<unsigned int>& rptids);
+	int updateCollectionEvent(unsigned int CEID, const char* name, const char* desc, const std::vector<unsigned int>& rptids);
 
-	// 取得CCollectionEvent
+	// 鍙栧緱CCollectionEvent
 	SERVO::CCollectionEvent* getEvent(unsigned short CEID);
 
-	// 取得Report
+	// 鍙栧緱Report
 	SERVO::CReport* getReport(int rptid);
 
 	void setListener(SECSListener listener);
@@ -185,14 +214,18 @@
 	int unserialize(const char* pszBuffer, int nBufferSize);
 
 public:
-	/* request开头的函数为主动发送数据的函数 */
+	/* request寮�澶寸殑鍑芥暟涓轰富鍔ㄥ彂閫佹暟鎹殑鍑芥暟 */
 	int requestAreYouThere();
 	int requestAlarmReport(int ALCD, int ALID, const char* ALTX);
 	int requestEventReportSend(unsigned int CEID);
 	int requestEventReportSend(const char* pszEventName);
 	int requestEventReportSend_CarrierID_Readed();
+	int requestEventReportSend_CheckSlotMap();
+	int requestEventReportSend_SlotMapVerificationOK();
+	int requestEventReportSend_SlotMapVerificationNG();
 	int requestEventReportSend_Port_Unload_Ready();
 	int requestEventReportSend_Port_Load_Ready();
+	int requestEventReportSend_Port_Ready_To_Release();
 	int requestEventReportSend_Port_Blocked();
 	int requestEventReportSend_PJ_Queued();
 	int requestEventReportSend_PJ_Start();
@@ -201,14 +234,23 @@
 	int requestEventReportSend_CJ_End();
 	int requestEventReportSend_Panel_Start();
 	int requestEventReportSend_Panel_End();
+	int requestEventReportSend_OCR_PanelID_Read_OK();
+	int requestEventReportSend_OCR_PanelID_Read(short vcrResult);
+	int requestEventReportSend_LoadPortNotAssoc();
+	int requestEventReportSend_ProcessDataReport();
+	int requestEventReportSend_SubEqpStart();
+	int requestEventReportSend_SubEqpEnd();
 
 private:
 	void replyAck(int s, int f, unsigned int systemBytes, BYTE ack, const char* pszAckName);
 
-	/* reply开头的函数为回复函数 */
+	/* reply寮�澶寸殑鍑芥暟涓哄洖澶嶅嚱鏁� */
 	int replyAreYouThere(IMessage* pRecv);
 	int replyEstablishCommunications(IMessage* pRecv);
 	int replySelectedEquipmentStatusData(IMessage* pRecv);
+	int replyStatusVariableNamelistRequest(IMessage* pRecv);  // S1F11/S1F12
+	int replyDataVariableNamelistRequest(IMessage* pRecv);    // S1F21/S1F22
+	int replyCollectionEventNamelistRequest(IMessage* pRecv); // S1F23/S1F24
 	int replyOnLine(IMessage* pRecv);
 	int replyOffLine(IMessage* pRecv);
 	int replyEquipmentConstantRequest(IMessage* pRecv);
@@ -223,6 +265,8 @@
 	int replyEanbleDisableAlarmReport(IMessage* pRecv);
 	int replyPurgeSpooledData(IMessage* pRecv);
 	int replyQueryPPIDList(IMessage* pRecv);
+	int replyDeletePPID(IMessage* pRecv); // S7F17/S7F18
+	int replyProcessProgramRequest(IMessage* pRecv); // S7F5/S7F6
 	int replyTerminalDisplay(IMessage* pRecv);
 	int replyCreateObj(IMessage* pRecv);
 	int replyPRJobMultiCreate(IMessage* pRecv);
@@ -232,7 +276,13 @@
 	inline void Unlock() { LeaveCriticalSection(&m_criticalSection); }
 	int onRecvMsg(IMessage* pMessage);
 	void clearAllVariabel();
+	void clearAllDataVariabel();
 	std::vector<unsigned int> parseVidList(CString& strNums);
+	int writeVariablesToFile(const std::string& filepath);
+	int writeDataVariablesToFile(const std::string& filepath);
+	int writeEquipmentConstantsToFile(const std::string& filepath);
+	int writeReportsToFile(const std::string& filepath);
+	int writeCollectionEventsToFile(const std::string& filepath);
 
 private:
 	CModel* m_pModel;
@@ -250,6 +300,21 @@
 
 private:
 	SECSListener m_listener;
+	std::string m_strVariableFilepath;
+	bool m_bVariableUtf8{ false };
+	bool m_bVariableUtf8Bom{ false };
+	std::string m_strDataVariableFilepath;
+	bool m_bDataVariableUtf8{ false };
+	bool m_bDataVariableUtf8Bom{ false };
+	std::string m_strReportFilepath;
+	bool m_bReportUtf8{ false };
+	bool m_bReportUtf8Bom{ false };
+	std::string m_strCollectionEventFilepath;
+	bool m_bCollectionUtf8{ false };
+	bool m_bCollectionUtf8Bom{ false };
+	std::string m_strEquipmentConstantFilepath;
+	bool m_bEquipmentConstantUtf8{ false };
+	bool m_bEquipmentConstantUtf8Bom{ false };
 	BOOL m_bCimWorking;
 	HANDLE m_hCimWorkEvent;
 	HANDLE m_hCimWorkThreadHandle;
@@ -260,6 +325,17 @@
 private:
 	// CVariable vector
 	std::vector<SERVO::CVariable*> m_variabels;
+	// CDataVariable vector
+	std::vector<SERVO::CDataVariable*> m_dataVariabels;
+	// Equipment constants
+	struct EquipmentConstantEntry {
+		unsigned int id{ 0 };
+		std::string name;
+		std::string format;
+		std::string remark;
+		std::string value;
+	};
+	std::vector<EquipmentConstantEntry> m_equipmentConstants;
 
 	// CReport vector
 	std::vector<SERVO::CReport*> m_reports;
@@ -267,7 +343,13 @@
 	// CollectionEvent vector
 	std::vector<SERVO::CCollectionEvent*> m_collectionEvents;
 
-	// Spooling Config
-	std::map<uint16_t, std::set<uint16_t>> m_spoolingConfig;
-};
+	// Spooling blacklist: StreamId -> {FunctionId...}
+	// In this map means DO NOT spool/cache.
+	// Special case: stream 1 is not spooled regardless of config.
+	// If a stream key exists with empty set => blacklist ALL functions in that stream.
+	std::map<uint16_t, std::set<uint16_t>> m_spoolBlacklistByStream;
+	bool m_spoolingEnabled{ true };
 
+private:
+	bool shouldSpool(uint8_t streamId, uint8_t functionId) const;
+};
diff --git a/SourceCode/Bond/Servo/LoginDlg2.cpp b/SourceCode/Bond/Servo/LoginDlg2.cpp
new file mode 100644
index 0000000..3021f8e
--- /dev/null
+++ b/SourceCode/Bond/Servo/LoginDlg2.cpp
@@ -0,0 +1,103 @@
+锘�// LoginDlg.cpp: 瀹炵幇鏂囦欢
+//
+
+#include "stdafx.h"
+#include "Servo.h"
+#include "afxdialogex.h"
+#include "LoginDlg2.h"
+
+
+// CLoginDlg 瀵硅瘽妗�
+
+IMPLEMENT_DYNAMIC(CLoginDlg2, CDialogEx)
+
+CLoginDlg2::CLoginDlg2(CWnd* pParent /*=nullptr*/)
+	: CDialogEx(IDD_DIALOG_LOGIN, pParent)
+{
+}
+
+CLoginDlg2::~CLoginDlg2()
+{
+}
+
+void CLoginDlg2::DoDataExchange(CDataExchange* pDX)
+{
+    CDialogEx::DoDataExchange(pDX);
+}
+
+
+BEGIN_MESSAGE_MAP(CLoginDlg2, CDialogEx)
+	ON_BN_CLICKED(IDC_BUTTON_LOGIN, &CLoginDlg2::OnBnClickedLogin)
+    ON_STN_CLICKED(IDC_STATIC_CHANGE_PASSWORD, &CLoginDlg2::OnBnClickedChangePassword)
+END_MESSAGE_MAP()
+
+
+// CLoginDlg 娑堟伅澶勭悊绋嬪簭
+
+
+BOOL CLoginDlg2::OnInitDialog()
+{
+    CDialog::OnInitDialog();
+
+    // 璁剧疆绐楀彛鏍囬鍜屽垵濮嬪��
+    SetWindowText(_T("鐧诲綍"));
+
+
+    CStatic* pStaticImage = (CStatic*)GetDlgItem(IDC_STATIC_IMAGE);
+    ASSERT(pStaticImage);
+
+    CString strIconPath;
+    strIconPath.Format(_T("%s\\Res\\Operator_High_32.ico"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir);
+    HICON hIcon = (HICON)::LoadImage(
+        nullptr,
+        strIconPath,
+        IMAGE_ICON,
+        32, // 鍥炬爣瀹藉害
+        32, // 鍥炬爣楂樺害
+        LR_LOADFROMFILE);
+    if (hIcon) {
+        // 璁剧疆 CStatic 鎺т欢涓哄浘鏍囨牱寮�
+        pStaticImage->ModifyStyle(0xF, SS_ICON);
+        pStaticImage->SetIcon(hIcon);
+    }
+
+    // 娣诲姞SS_NOTIFY鏍峰紡
+    CStatic* pStatic = (CStatic*)GetDlgItem(IDC_STATIC_CHANGE_PASSWORD);
+    if (pStatic != nullptr) {
+        pStatic->ModifyStyle(0, SS_NOTIFY);
+    }
+
+    GetDlgItem(IDC_CHECK_REMEMBER_PASSWORD)->ShowWindow(SW_HIDE);
+
+
+    // test
+    SetDlgItemText(IDC_EDIT_USERNAME, _T("admin"));
+    SetDlgItemText(IDC_EDIT_PASSWORD, _T("admin123"));
+
+    return TRUE;
+}
+
+void CLoginDlg2::OnBnClickedLogin()
+{
+    GetDlgItemText(IDC_EDIT_USERNAME, m_strUsername);
+    GetDlgItemText(IDC_EDIT_PASSWORD, m_strPassword);
+
+    if (m_strUsername.IsEmpty()) {
+        AfxMessageBox(_T("璇疯緭鍏ョ敤鎴峰悕"));
+        GetDlgItem(IDC_EDIT_USERNAME)->SetFocus();
+        return;
+    }
+    if (m_strPassword.IsEmpty()) {
+        AfxMessageBox(_T("璇疯緭鍏ュ瘑鐮�"));
+        GetDlgItem(IDC_EDIT_PASSWORD)->SetFocus();
+        return;
+}
+
+
+    EndDialog(IDOK);
+}
+
+void CLoginDlg2::OnBnClickedChangePassword()
+{
+
+}
\ No newline at end of file
diff --git a/SourceCode/Bond/Servo/LoginDlg2.h b/SourceCode/Bond/Servo/LoginDlg2.h
new file mode 100644
index 0000000..335037d
--- /dev/null
+++ b/SourceCode/Bond/Servo/LoginDlg2.h
@@ -0,0 +1,30 @@
+锘�#pragma once
+#include "afxdialogex.h"
+
+
+// CLoginDlg 瀵硅瘽妗�
+
+class CLoginDlg2 : public CDialogEx
+{
+	DECLARE_DYNAMIC(CLoginDlg2)
+
+public:
+	CLoginDlg2(CWnd* pParent = nullptr);   // 鏍囧噯鏋勯�犲嚱鏁�
+	virtual ~CLoginDlg2();
+
+// 瀵硅瘽妗嗘暟鎹�
+#ifdef AFX_DESIGN_TIME
+	enum { IDD = IDD_DIALOG_LOGIN };
+#endif
+
+public:
+	CString m_strUsername;
+	CString m_strPassword;
+
+protected:
+	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 鏀寔
+	virtual BOOL OnInitDialog();
+	afx_msg void OnBnClickedLogin();
+	afx_msg void OnBnClickedChangePassword();
+	DECLARE_MESSAGE_MAP()
+};
diff --git a/SourceCode/Bond/Servo/Model.cpp b/SourceCode/Bond/Servo/Model.cpp
index 8530738..e5afc98 100644
--- a/SourceCode/Bond/Servo/Model.cpp
+++ b/SourceCode/Bond/Servo/Model.cpp
@@ -1,4 +1,4 @@
-#include "stdafx.h"
+锘�#include "stdafx.h"
 #include "Model.h"
 #include "Log.h"
 #include "Common.h"
@@ -9,6 +9,13 @@
 #include "TransferManager.h"
 #include "RecipeManager.h"
 #include "GlassLogDb.h"
+#include "CParam.h"
+#include "CJobDataS.h"
+#include <algorithm>
+#include <iomanip>
+#include <sstream>
+#include <array>
+#include <map>
 
 
 CModel::CModel()
@@ -21,11 +28,111 @@
 {
 }
 
+void CModel::refreshDerivedSVs()
+{
+	// CJobSpace: how many ControlJobs can be created (current implementation supports 0/1).
+	m_hsmsPassive.setVariableValue("CJobSpace", (__int64)(m_master.canCreateControlJob() ? 1 : 0));
+
+	// PJobSpace: how many ProcessJobs can be created (current implementation supports 0/1).
+	m_hsmsPassive.setVariableValue("PJobSpace", (__int64)(m_master.isProcessJobsEmpty() ? 1 : 0));
+}
+
+void CModel::notifyControlJobChanged()
+{
+	// 1) 鍒锋柊娲剧敓 SV
+	refreshDerivedSVs();
+	// 2) 閫氱煡涓婂眰 UI锛圧X_CODE_CONTROLJOB_CHANGED锛�
+	notify(RX_CODE_CONTROLJOB_CHANGED);
+}
+
+bool CModel::raiseSoftAlarm(int alarmId,
+	const std::string& desc,
+	int level /*= -1*/,
+	int deviceId /*= 0*/,
+	int unitId /*= 0*/,
+	const char* deviceName /*= "Software"*/,
+	const char* unitName /*= "App"*/)
+{
+	AlarmManager& alarmManager = AlarmManager::getInstance();
+	const AlarmInfo* info = alarmManager.getAlarmInfoByID(alarmId);
+
+	int severity = level;
+	if (severity < 0 && info != nullptr) severity = info->nAlarmLevel;
+	if (severity < 0) severity = 0;
+
+	std::string descText = desc;
+	if (descText.empty() && info != nullptr) {
+		descText = !info->strDescription.empty() ? info->strDescription : info->strAlarmText;
+	}
+	if (descText.empty()) {
+		descText = CToolUnits::formatString("Alarm %d", alarmId);
+	}
+
+	AlarmData alarmData;
+	alarmData.nId = alarmId;
+	alarmData.nSeverityLevel = severity;
+	alarmData.nDeviceId = deviceId;
+	alarmData.nUnitId = unitId;
+	alarmData.strDeviceName = deviceName;
+	alarmData.strUnitName = unitName;
+	// 鑻ユ湭鏄惧紡鎻愪緵璁惧/鍗曞厓鍚嶇О锛屽皾璇曢�氳繃 deviceId/unitId 瑙f瀽锛坰oft alarm 榛樿鍧囦负 0锛�
+	if (alarmData.strDeviceName.empty()) {
+		alarmData.strDeviceName = alarmManager.getDeviceNameById(deviceId);
+	}
+	if (alarmData.strUnitName.empty()) {
+		alarmData.strUnitName = alarmManager.getUnitNameById(deviceId, unitId);
+	}
+	alarmData.strStartTime = CToolUnits::timeToString2(CToolUnits::getTimestamp());
+	alarmData.strEndTime = "";
+	alarmData.strDescription = descText;
+
+	int nAlarmEventId = 0;
+	bool result = alarmManager.addAlarm(alarmData, nAlarmEventId);
+	if (result) {
+		notify(RX_CODE_ALARM_SET);
+		if (m_master.isAlarmReportEnable()) {
+			m_hsmsPassive.requestAlarmReport(1, alarmId, descText.c_str());
+		}
+	}
+	return result;
+}
+
+void CModel::clearSoftAlarm(int alarmId, int deviceId, int unitId)
+{
+	AlarmManager& alarmManager = AlarmManager::getInstance();
+	alarmManager.clearAlarmByAttributes(alarmId, deviceId, unitId, CToolUnits::getCurrentTimeString());
+	notify(RX_CODE_ALARM_CLEAR);
+	if (m_master.isAlarmReportEnable()) {
+		const AlarmInfo* info = alarmManager.getAlarmInfoByID(alarmId);
+		std::string descText;
+		if (info != nullptr) descText = info->strAlarmText;
+		m_hsmsPassive.requestAlarmReport(0, alarmId, descText.c_str());
+	}
+}
+
+void CModel::setControlState(ControlState newState)
+{
+	const auto prev = m_currentControlState;
+	if (newState != m_currentControlState) {
+		m_currentControlState = newState;
+		// S6F11 (CEID=600): ControlStateChanged
+		m_hsmsPassive.withVariableLock([&] {
+			m_hsmsPassive.setVariableValue("PreviousControlState", (__int64)static_cast<uint8_t>(prev));
+			m_hsmsPassive.setVariableValue("CurrentControlState", (__int64)static_cast<uint8_t>(m_currentControlState));
+			m_hsmsPassive.requestEventReportSend("ControlStateChanged");
+		});
+		notifyInt(RX_CODE_CONTROL_STATE_CHANGED, static_cast<int>(m_currentControlState));
+	} else {
+		// Keep SV in sync even if unchanged/load-time refresh.
+		m_hsmsPassive.setVariableValue("CurrentControlState", (__int64)static_cast<uint8_t>(m_currentControlState));
+	}
+}
+
 IObservable* CModel::getObservable()
 {
 	if (m_pObservable == nullptr) {
 		m_pObservable = RX_AllocaObservable([&](IObservableEmitter* e) -> void {
-			m_pObservableEmitter = e;			// 保存发射器
+			m_pObservableEmitter = e;			// 淇濆瓨鍙戝皠鍣�
 		});
 	}
 
@@ -77,19 +184,23 @@
 
 int CModel::init()
 {
+	const ULONGLONG boot_model_begin = GetTickCount64();
 	CString strIniFile;
 	CString strUnitId;
 	strIniFile.Format(_T("%s\\ServoConfiguration.ini"), (LPTSTR)(LPCTSTR)m_strWorkDir);
 	m_configuration.setFilepath((LPTSTR)(LPCTSTR)strIniFile);
 	m_configuration.getUnitId(strUnitId);
 
-	// 机器型号和软件版本号应从配置中读取,当前先固定值
+	// 鏈哄櫒鍨嬪彿鍜岃蒋浠剁増鏈彿搴斾粠閰嶇疆涓鍙栵紝褰撳墠鍏堝浐瀹氬��
 	CString strModeType = _T("Master");
 	CString strSoftRev = _T("1.0.2");
 
 
 	// CGlassPool
 	m_glassPool.initPool();
+
+	// 灏� Model 涓婁笅鏂囦紶閫掔粰 Master锛屼究浜� Master 瑙﹀彂杞欢绾ф姤璀︾瓑璺ㄥ眰鎿嶄綔
+	m_master.setModelCtx(this);
 
 
 	// Log
@@ -104,31 +215,35 @@
 	CLog::GetLog()->SetLogsDir(strLogDir);
 	CLog::GetLog()->SetEquipmentId((LPTSTR)(LPCTSTR)strUnitId);
 	LOGI("\r\n\r\n~~~ Prog Start! ~~~");
+	LOGI("[BOOT][MODEL] init begin");
 
 
 	SECSListener listener;
-	listener.onEQOffLine = [&](void* pFrom) -> void {
-		LOGI("远程请求OffLine");
+	listener.onEQOffLine = [this](void* pFrom) -> void {
+		LOGI("杩滅▼璇锋眰OffLine");
+		(void)pFrom;
+		setControlState(ControlState::OfflineHost);
 	};
-	listener.onEQOnLine = [&](void* pFrom) -> void {
-		LOGI("远程请求OnLine");
+	listener.onEQOnLine = [this](void* pFrom) -> void {
+		LOGI("杩滅▼璇锋眰OnLine");
+		(void)pFrom;
+		// Customer flow: S1F17 RequestOnline defaults to OnlineRemote.
+		setControlState(ControlState::OnlineRemote);
 	};
-	listener.onCommand = [&](void* pFrom, const char* pszName, std::vector<CommandParameter>& params) -> void {
+	listener.onCommand = [this](void* pFrom, const char* pszName, std::vector<CommandParameter>& params) -> void {
 		LOGI("onCommand:%s", pszName);
+		(void)pFrom;
 		for (auto& item : params) {
 			LOGI("param:%s,%s", item.szName, item.szValue);
 		}
-	};
-	listener.onEQConstantRequest = [&](void* pFrom, std::vector<EQConstant>& eqcs) -> void {
-		// 在此填充常量值,目前仅是加1后返回
-		for (auto& item : eqcs) {
-			sprintf_s(item.szValue, 256, "Test%d", item.id + 1);
+
+		if (pszName == nullptr) return;
+		// S2F41 GoLocal / GoRemote (RCMD)
+		if (_strcmpi(pszName, "GoLocal") == 0 || _strcmpi(pszName, "LOCAL") == 0 || _strcmpi(pszName, "GoLOCAL") == 0) {
+			setControlState(ControlState::OnlineLocal);
 		}
-	};
-	listener.onEQConstantSend = [&](void* pFrom, std::vector<EQConstant>& eqcs) -> void {
-		// 在此保存和设置机器常量值
-		for (auto& item : eqcs) {
-			LOGI("onEQConstantRequest: %d, %s", item.id, item.szValue);
+		else if (_strcmpi(pszName, "GoRemote") == 0 || _strcmpi(pszName, "REMOTE") == 0 || _strcmpi(pszName, "GoREMOTE") == 0) {
+			setControlState(ControlState::OnlineRemote);
 		}
 	};
 	listener.onDatetimeSync = [&](void* pFrom, SYSTEMTIME& time) -> void {
@@ -141,6 +256,21 @@
 		if (ids.empty()) {
 			m_master.enableEventReport(bEnable);
 		}
+	};
+	listener.onDeletePPID = [&](void* pFrom, const std::vector<std::string>& ppids) -> bool {
+		(void)pFrom;
+		bool allOk = true;
+		std::vector<std::string> targets = ppids;
+		if (targets.empty()) {
+			// L:0 => delete all PPIDs
+			targets = RecipeManager::getInstance().getAllPPID();
+		}
+		for (auto& ppid : targets) {
+			bool ok = RecipeManager::getInstance().deleteRecipeByPPID(ppid);
+			allOk = allOk && ok;
+			LOGI("<CModel>DeletePPID: %s, result=%s", ppid.c_str(), ok ? "OK" : "FAIL");
+		}
+		return allOk;
 	};
 	listener.onEnableDisableAlarmReport = [&](void* pFrom, bool bEnable, unsigned int id) -> void {
 		LOGI("onEnableDisableAlarmReport bEnable:%s, id:%d", bEnable ? _T("YES") : _T("NO"), id);
@@ -172,23 +302,90 @@
 				return CAACK_3;
 			}
 
+			const unsigned int portIndex = PTN - 1;
+			SERVO::CLoadPort* pLoadPort = (SERVO::CLoadPort*)m_master.getEquipment(EQ_ID_LOADPORT1 + portIndex);
+			LOGI("<Model>onCarrierAction %d, %s, %d, %d", DATAID, pszCarrierAction, pszCarrierId, PTN);
+
 			if (_strcmpi(pszCarrierAction, "ProceedWithCarrier") == 0) {
-				m_master.proceedWithCarrier(PTN);
+				// 鏂囨。娴佺▼锛歅roceedWithCarrier 涔嬪悗璁惧杩涘叆 Check SlotMap锛圵FH锛夛紝
+				// 鐪熸鐨勨�滃紑濮嬧�濈敱 ProceedWithSlotMap 鍐崇瓥瑙﹀彂銆�
+				// 浠呭綋鏈紑鍚� CompareMapsBeforeProceeding 鏃讹紝鎵嶆部鐢ㄦ棫閫昏緫鐩存帴 Start銆�
+				LOGI("<CModel>ProceedWithCarrier");
+				if (m_master.getControlJob() == nullptr || m_master.isProcessJobsEmpty()) {
+					strErrorTxt = "rejected - ControlJob/ProcessJob not ready";
+					LOGW("<CModel>ProceedWithCarrier rejected: no CJ/PJ, port=%d", portIndex + 1);
+					return CAACK_5;
+				}
+				if (pLoadPort == nullptr || !pLoadPort->isCompareMapsBeforeProceeding()) {
+					m_master.proceedWithCarrier(portIndex);
+				}
+				return CAACK_0;
+			}
+			else if (_strcmpi(pszCarrierAction, "ProceedWithSlotMap") == 0) {
+				// TODO(Host鍗忓晢):
+				// 鏂囨。涓� ProceedWithSlotMap 鍙兘浼氭惡甯� LotID / PanelIDList / SlotMap 绛夋暟鎹紙鏈�澶�13鐗囷級鐢ㄤ簬鏍煎紡鏍¢獙涓庣粦瀹氥��
+				// 褰撳墠 S3F17 瑙f瀽缁撴瀯浠呮敮鎸� {DATAID, CarrierAction, CarrierID, PTN}锛屽皻鏈疄鐜颁笂杩版墿灞曞瓧娈电殑瑙f瀽/鏍¢獙銆�
+				// 鏈潵鑻ュ鎴风‘璁� SECS-II 缁撴瀯锛岄渶瑕佸湪 CHsmsPassive::replyCarrierAction() 鎵╁睍瑙f瀽骞跺湪姝ゅ钀藉簱/鏍¢獙銆�
+				// 浠呭湪 CompareMapsBeforeProceeding 鍚敤锛圚ost 妯″紡锛変笅鍏佽姝ゅ姩浣�
+				LOGI("<CModel>ProceedWithSlotMap");
+				if (pLoadPort == nullptr || !pLoadPort->isCompareMapsBeforeProceeding()) {
+					strErrorTxt = "rejected - SlotMap check disabled";
+					return CAACK_5;
+				}
+
+				const short scanMap = pLoadPort->getScanCassetteMap();
+				const short downloadMap = pLoadPort->getDownloadCassetteMap();
+				m_hsmsPassive.withVariableLock([&] {
+					m_hsmsPassive.setVariableValue("SlotMapScan", scanMap);
+					m_hsmsPassive.setVariableValue("SlotMapDownload", downloadMap);
+					if (scanMap != downloadMap) {
+						m_hsmsPassive.requestEventReportSend_SlotMapVerificationNG();
+						m_hsmsPassive.requestEventReportSend("SlotMapMismatch");
+					}
+					else {
+						m_hsmsPassive.requestEventReportSend_SlotMapVerificationOK();
+					}
+				});
+
+				if (scanMap != downloadMap) {
+					strErrorTxt = "rejected - SlotMap mismatch";
+					return CAACK_5;
+				}
+
+				// Host 纭 SlotMap 鍚庡啀寮�濮嬪姞宸�/娴佺▼
+				m_master.proceedWithCarrier(portIndex);
 				return CAACK_0;
 			}
 			else if (_strcmpi(pszCarrierAction, "CarrierRelease") == 0) {
-				m_master.carrierRelease(PTN);
+				LOGI("<CModel>CarrierRelease");
+				m_master.carrierRelease(portIndex);
 				return CAACK_0;
 			}
 
 			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());
 		}
+
+		auto rejectAll = [&](uint32_t code, const std::string& msg) -> int {
+			LOGW("<Model>onPRJobMultiCreate rejected: %s", msg.c_str());
+			for (auto p : pjs) {
+				if (p != nullptr) p->addIssue(code, msg);
+			}
+			return -1;
+		};
+
+		// 鍗� PJ 妯″紡锛氬彧鎺ュ彈 1 鏉′笖褰撳墠鏃犲湪鍒� PJ
+		if (pjs.size() != 1) {
+			return rejectAll(1200, "Only 1 ProcessJob supported (single-PJ mode)");
+		}
+		if (!m_master.isProcessJobsEmpty()) {
+			return rejectAll(1201, "ProcessJob exists, cannot create new in single-PJ mode");
+		}
+
 		int nRet = m_master.setProcessJobs(pjs);
 		auto processJobs = m_master.getProcessJobs();
 		std::vector<SERVO::CVariable> vars;
@@ -198,8 +395,10 @@
 			vars.push_back(var);
 		}
 
-		m_hsmsPassive.setVariableValue("PJQueued", vars);
-		m_hsmsPassive.requestEventReportSend_PJ_Queued();
+		m_hsmsPassive.withVariableLock([&] {
+			m_hsmsPassive.setVariableValue("PJQueued", vars);
+			m_hsmsPassive.requestEventReportSend_PJ_Queued();
+		});
 		return nRet;
 	};
 	listener.onControlJobCreate = [&](void* pFrom, SERVO::CControlJob& controlJob) -> int {
@@ -213,20 +412,47 @@
 	CString strVarialbleFile;
 	strVarialbleFile.Format(_T("%s\\VariableList.txt"), (LPTSTR)(LPCTSTR)m_strWorkDir);
 	m_hsmsPassive.loadVarialbles((LPTSTR)(LPCTSTR)strVarialbleFile);
+	strVarialbleFile.Format(_T("%s\\DataVariableList.txt"), (LPTSTR)(LPCTSTR)m_strWorkDir);
+	m_hsmsPassive.loadDataVarialbles((LPTSTR)(LPCTSTR)strVarialbleFile);
+	strVarialbleFile.Format(_T("%s\\EquipmentConstantList.txt"), (LPTSTR)(LPCTSTR)m_strWorkDir);
+	m_hsmsPassive.loadEquipmentConstants((LPTSTR)(LPCTSTR)strVarialbleFile);
+	setControlState(m_currentControlState);
+	refreshDerivedSVs();
 	m_hsmsPassive.init(this, "APP", 7000);
 	strVarialbleFile.Format(_T("%s\\ReportList.txt"), (LPTSTR)(LPCTSTR)m_strWorkDir);
 	m_hsmsPassive.loadReports((LPTSTR)(LPCTSTR)strVarialbleFile);
 	strVarialbleFile.Format(_T("%s\\CollectionEventList.txt"), (LPTSTR)(LPCTSTR)m_strWorkDir);
 	m_hsmsPassive.loadCollectionEvents((LPTSTR)(LPCTSTR)strVarialbleFile);
+	{
+		auto events = m_hsmsPassive.getCollectionEvents();
+		std::vector<unsigned int> ceids;
+		ceids.reserve(events.size());
+		for (auto e : events) {
+			if (e != nullptr) ceids.push_back(e->getEventId());
+		}
+		m_master.setAllowedCeids(ceids);
+	}
 	strVarialbleFile.Format(_T("%s\\HsmsPassive.cache"), (LPTSTR)(LPCTSTR)m_strWorkDir);
 	m_hsmsPassive.loadCacheFromFile(strVarialbleFile);
+	LOGI("[BOOT][MODEL] HSMS config loaded, cost=%llu ms",
+		(unsigned long long)(GetTickCount64() - boot_model_begin));
 
 
 	SERVO::MasterListener masterListener;
+	auto formatParamValue = [](const CParam& p) {
+		std::ostringstream oss;
+		oss.setf(std::ios::fixed);
+		oss << std::setprecision(4) << p.getDoubleValue();
+		return oss.str();
+	};
 	masterListener.onMasterStateChanged = [&](void* pMaster, SERVO::MASTERSTATE state) -> void {
 		LOGI("<CModel>Master state changed(%d)", (int)state);
 		notify(RX_CODE_MASTER_STATE_CHANGED);
 	};
+	masterListener.onControlJobChanged = [this](void* pMaster) {
+		(void)pMaster;
+		this->notifyControlJobChanged();
+		};
 	masterListener.onEqAlive = [&](void* pMaster, SERVO::CEquipment* pEquipment, BOOL bAlive) -> void {
 		LOGI("<CModel>Equipment onAlive:%s(%s).", pEquipment->getName().c_str(),
 			bAlive ? _T("ON") : _T("OFF"));
@@ -296,6 +522,15 @@
 	};
 	masterListener.onEqVcrEventReport = [&](void* pMaster, SERVO::CEquipment* pEquipment, SERVO::CVcrEventReport* pReport) {
 		LOGE("<CModel>onEqVcrEventReport.");
+		if (pReport != nullptr) {
+			m_hsmsPassive.withVariableLock([&] {
+				m_hsmsPassive.setVariableValue("VCRPanelID", pReport->getGlassId().c_str());
+				int nRet = m_hsmsPassive.requestEventReportSend_OCR_PanelID_Read(pReport->getVcrResult());
+				if (nRet != ER_NOERROR) {
+					LOGE("<CModel>requestEventReportSend_OCR_PanelID_Read failed, ret=%d", nRet);
+				}
+			});
+		}
 	};
 	masterListener.onEqDataChanged = [&](void* pMaster, SERVO::CEquipment* pEquipment, int code) {
 		LOGE("<CModel>onEqDataChanged.");
@@ -303,11 +538,11 @@
 	};
 	masterListener.onRobotTaskEvent = [&](void* pMaster, SERVO::CRobotTask* pTask, int code) {
 		if (pTask == nullptr) {
-			LOGE("<CModel>onRobotTaskEvent: 空任务指针,忽略事件 code=%d", code);
+			LOGE("<CModel>onRobotTaskEvent: 绌轰换鍔℃寚閽堬紝蹇界暐浜嬩欢 code=%d", code);
 			return;
 		}
 
-		// 任务描述与 ID 用于日志
+		// 浠诲姟鎻忚堪涓� ID 鐢ㄤ簬鏃ュ織
 		SERVO::CGlass* pGlass = (SERVO::CGlass*)pTask->getContext();
 		const std::string& strDesc = pTask->getDescription();
 		std::string strClassID;
@@ -319,48 +554,48 @@
 			}
 		}
 
-		// 日志输出与状态处理
+		// 鏃ュ織杈撳嚭涓庣姸鎬佸鐞�
 		switch (code) {
 		case ROBOT_EVENT_CREATE:
-			LOGI("<CModel>onRobotTaskEvent: 新任务创建(%s, ClassID=%s).", strDesc.c_str(), strClassID.c_str());
+			LOGI("<CModel>onRobotTaskEvent: 鏂颁换鍔″垱寤�(%s, ClassID=%s).", strDesc.c_str(), strClassID.c_str());
 			break;
 		case ROBOT_EVENT_FINISH:
-			LOGI("<CModel>onRobotTaskEvent: 任务完成(%s, ClassID=%s).", strDesc.c_str(), strClassID.c_str());
+			LOGI("<CModel>onRobotTaskEvent: 浠诲姟瀹屾垚(%s, ClassID=%s).", strDesc.c_str(), strClassID.c_str());
 			break;
 		case ROBOT_EVENT_ERROR:
-			LOGE("<CModel>onRobotTaskEvent: 任务错误(%s, ClassID=%s).", strDesc.c_str(), strClassID.c_str());
+			LOGE("<CModel>onRobotTaskEvent: 浠诲姟閿欒(%s, ClassID=%s).", strDesc.c_str(), strClassID.c_str());
 			break;
 		case ROBOT_EVENT_ABORT:
-			LOGE("<CModel>onRobotTaskEvent: 任务停止(%s, ClassID=%s).", strDesc.c_str(), strClassID.c_str());
+			LOGE("<CModel>onRobotTaskEvent: 浠诲姟鍋滄(%s, ClassID=%s).", strDesc.c_str(), strClassID.c_str());
 			break;
 		case ROBOT_EVENT_RESTORE:
-			LOGE("<CModel>onRobotTaskEvent: 任务回撤(%s, ClassID=%s).", strDesc.c_str(), strClassID.c_str());
+			LOGE("<CModel>onRobotTaskEvent: 浠诲姟鍥炴挙(%s, ClassID=%s).", strDesc.c_str(), strClassID.c_str());
 			break;
 		default:
-			LOGE("<CModel>onRobotTaskEvent: 未知事件 code=%d, 任务=%s", code, strDesc.c_str());
+			LOGE("<CModel>onRobotTaskEvent: 鏈煡浜嬩欢 code=%d, 浠诲姟=%s", code, strDesc.c_str());
 			break;
 		}
 
-		// 安全格式化时间
+		// 瀹夊叏鏍煎紡鍖栨椂闂�
 		auto format_time = [](time_t t) -> std::string {
 			if (t <= 0 || t == _I64_MIN || t == _I64_MAX) { 
 				return "";
 			}
 
-			// 使用 localtime_s 确保线程安全
+			// 浣跨敤 localtime_s 纭繚绾跨▼瀹夊叏
 			tm tmBuf{};
 			errno_t err = localtime_s(&tmBuf, &t);
 			if (err != 0 || tmBuf.tm_mon < 0 || tmBuf.tm_mon > 11) {
 				return "";
 			}
 
-			// 格式化时间字符串
+			// 鏍煎紡鍖栨椂闂村瓧绗︿覆
 			char buf[64] = {};
 			strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tmBuf);
 			return std::string(buf);
 		};
 
-		// 构造 TransferData 数据结构
+		// 鏋勯�� TransferData 鏁版嵁缁撴瀯
 		TransferData data;
 		data.strClassID = strClassID;
 		data.strCreateTime = format_time(pTask->getCreateTime());
@@ -369,7 +604,7 @@
 		data.strEndTime = format_time(pTask->getFinishTime());
 		data.strDescription = pTask->getSimpleDescription();
 
-		// 状态映射
+		// 鐘舵�佹槧灏�
 		static const char* STATUS_STR[] = {
 			"Ready", "Running", "Picking", "Picked", "Placing", "Restoring", "Error", "Abort", "Restored", "Completed"
 		};
@@ -382,77 +617,360 @@
 			data.strStatus = "Unknown";
 		}
 
-		// 写入数据库
+		// 鍐欏叆鏁版嵁搴�
 		if (code == ROBOT_EVENT_FINISH || code == ROBOT_EVENT_ERROR
 			|| code == ROBOT_EVENT_ABORT || code == ROBOT_EVENT_RESTORE) {
 			int nRecordId = 0;
 			TransferManager::getInstance().addTransferRecord(data, nRecordId);
-			LOGI("<CModel>onRobotTaskEvent: 任务记录已保存,RecordID=%d", nRecordId);
+			LOGI("<CModel>onRobotTaskEvent: 浠诲姟璁板綍宸蹭繚瀛橈紝RecordID=%d", nRecordId);
 		}
 		notifyPtrAndInt(RX_CODE_EQ_ROBOT_TASK, pTask, nullptr, code);
 
 	};
+	masterListener.onJobReceived = [&](void* pMaster, SERVO::CEquipment* pEquipment, int port, SERVO::CJobDataS* pJobDataS) {
+		(void)pMaster;
+		(void)port;
+		if (pEquipment == nullptr || pJobDataS == nullptr) return;
+		{
+			const std::string& g1 = pJobDataS->getGlass1Id();
+			const std::string& g2 = pJobDataS->getGlass2Id();
+			std::string glassId;
+			if (!g1.empty() && !g2.empty()) {
+				glassId = g1 + "+" + g2;
+			}
+			else if (!g1.empty()) {
+				glassId = g1;
+			}
+			else {
+				glassId = g2;
+			}
+			const int slotNo = pJobDataS->getTargetSlotNo();
+			m_hsmsPassive.withVariableLock([&] {
+				m_hsmsPassive.setVariableValue("SubEqpName", pEquipment->getName().c_str());
+				m_hsmsPassive.setVariableValue("SubEqpSlot", slotNo);
+				m_hsmsPassive.setVariableValue("MaterialId", glassId.c_str());
+				m_hsmsPassive.requestEventReportSend("GlassReceivedJob");
+			});
+		}
+		const int eqId = pEquipment->getID();
+		const int recipeId = pJobDataS->getMasterRecipe();
+		std::string recipe = RecipeManager::getInstance().getPPIDById(recipeId);
+		if (recipe.empty()) {
+			recipe = std::to_string(recipeId);
+		}
+		const std::string prev = pEquipment->getCurrentRecipe();
+		if (recipe.empty() || recipe == prev) {
+			pEquipment->setCurrentRecipe(recipe);
+			return;
+		}
+		pEquipment->setCurrentRecipe(recipe);
+		m_hsmsPassive.withVariableLock([&] {
+			m_hsmsPassive.setVariableValue("Clock", CToolUnits::getCurrentTimeString().c_str());
+			m_hsmsPassive.setVariableValue("EQPPExecName", recipe.c_str());
+			m_hsmsPassive.setVariableValue("SubEqpName", pEquipment->getName().c_str());
+			const char* recipeVid = nullptr;
+			switch (eqId) {
+			case EQ_ID_Bonder1: recipeVid = "Bonder1CurrentRecipe"; break;
+			case EQ_ID_Bonder2: recipeVid = "Bonder2CurrentRecipe"; break;
+			case EQ_ID_VACUUMBAKE: recipeVid = "VacuumBakeCurrentRecipe"; break;
+			case EQ_ID_BAKE_COOLING: recipeVid = "BakeCoolingCurrentRecipe"; break;
+			case EQ_ID_MEASUREMENT: recipeVid = "MeasurementCurrentRecipe"; break;
+			case EQ_ID_EFEM: recipeVid = "EFEMCurrentRecipe"; break;
+			default: break;
+			}
+			if (recipeVid != nullptr) {
+				m_hsmsPassive.setVariableValue(recipeVid, recipe.c_str());
+			}
+			m_hsmsPassive.requestEventReportSend("RecipeChanged");
+		});
+	};
+	masterListener.onJobSentOut = [&](void* pMaster, SERVO::CEquipment* pEquipment, int port, SERVO::CJobDataS* pJobDataS) {
+		(void)pMaster;
+		(void)port;
+		if (pEquipment == nullptr || pJobDataS == nullptr) return;
+		const std::string& g1 = pJobDataS->getGlass1Id();
+		const std::string& g2 = pJobDataS->getGlass2Id();
+		std::string glassId;
+		if (!g1.empty() && !g2.empty()) {
+			glassId = g1 + "+" + g2;
+		}
+		else if (!g1.empty()) {
+			glassId = g1;
+		}
+		else {
+			glassId = g2;
+		}
+		const int slotNo = pJobDataS->getSourceSlotNo();
+		m_hsmsPassive.withVariableLock([&] {
+			m_hsmsPassive.setVariableValue("SubEqpName", pEquipment->getName().c_str());
+			m_hsmsPassive.setVariableValue("SubEqpSlot", slotNo);
+			m_hsmsPassive.setVariableValue("MaterialId", glassId.c_str());
+			m_hsmsPassive.requestEventReportSend("GlassSentOutJob");
+		});
+	};
 	masterListener.onLoadPortStatusChanged = [&] (void* pMaster, SERVO::CEquipment* pEquipment, short status, __int64 data) {
 		LOGE("<CModel>onLoadPortStatusChanged. status = %d", status);
+		static std::map<int, short> s_prevPortStatus;
+		const int eqId = (pEquipment != nullptr) ? pEquipment->getID() : 0;
+		const short prevStatus = s_prevPortStatus[eqId];
+		s_prevPortStatus[eqId] = status;
+		SERVO::CLoadPort* pLoadPort = dynamic_cast<SERVO::CLoadPort*>(pEquipment);
+
+		// Unified PortStateChange event + SV maintenance
+		if (pLoadPort != nullptr) {
+			const unsigned int portIndex = pLoadPort->getIndex() + 1;
+			char stateVid[64] = {0};
+			char modeVid[64] = {0};
+			sprintf_s(stateVid, "PortTransferState_P%u", portIndex);
+			sprintf_s(modeVid, "AccessMode_P%u", portIndex);
+			m_hsmsPassive.withVariableLock([&] {
+				m_hsmsPassive.setVariableValue(stateVid, (__int64)status);
+				m_hsmsPassive.setVariableValue(modeVid, (__int64)pLoadPort->getPortMode());
+				m_hsmsPassive.setVariableValue("PortId", pLoadPort->getID());
+				m_hsmsPassive.setVariableValue("PortState", (__int64)status);
+				m_hsmsPassive.requestEventReportSend("PortStateChange");
+			});
+		}
+
 		if (status == PORT_INUSE) {
-			SERVO::CLoadPort* pLoadPort = dynamic_cast<SERVO::CLoadPort*>(pEquipment);
-			if (pLoadPort != nullptr) {
-				m_hsmsPassive.setVariableValue("CarrierID", pLoadPort->getCassetteId().c_str());
-			}
-			m_hsmsPassive.requestEventReportSend_CarrierID_Readed();
+			m_hsmsPassive.withVariableLock([&] {
+				if (pLoadPort != nullptr) {
+					const unsigned int portIndex = pLoadPort->getIndex() + 1;
+					char carrierVid[64] = {0};
+					sprintf_s(carrierVid, "CarrierID_P%u", portIndex);
+					m_hsmsPassive.setVariableValue(carrierVid, pLoadPort->getCassetteId().c_str());
+					if (prevStatus != PORT_INUSE && pLoadPort->isCompareMapsBeforeProceeding()) {
+						// TODO(Host鍗忓晢):
+						// 鏂囨。涓爣鏄庯細1-Empty锛�3-Exist锛屽洜姝ゆ垜浠彲鑳介渶瑕佸皢uint鐨刴ap杞崲涓簂ist涓婁紶 
+						m_hsmsPassive.setVariableValue("SlotMap", pLoadPort->getScanCassetteMap());
+						m_hsmsPassive.requestEventReportSend_CheckSlotMap();
+					}
+				}
+				m_hsmsPassive.requestEventReportSend_CarrierID_Readed();
+			});
 		}
 		else if (status == PORT_BLOCKED) {
 			SERVO::CLoadPort* pLoadPort = dynamic_cast<SERVO::CLoadPort*>(pEquipment);
-			if (pLoadPort != nullptr) {
-				m_hsmsPassive.setVariableValue("BlockedPortId", pLoadPort->getID());
-			}
-			m_hsmsPassive.requestEventReportSend_Port_Blocked();
+			m_hsmsPassive.withVariableLock([&] {
+				if (pLoadPort != nullptr) {
+					m_hsmsPassive.setVariableValue("PortId", pLoadPort->getID());
+				}
+				m_hsmsPassive.requestEventReportSend_Port_Blocked();
+			});
 		}
 		else if (status == PORT_LOAD_READY) {
 			SERVO::CLoadPort* pLoadPort = dynamic_cast<SERVO::CLoadPort*>(pEquipment);
-			if (pLoadPort != nullptr) {
-				m_hsmsPassive.setVariableValue("LoadReadyPortId", pLoadPort->getID());
-			}
-			m_hsmsPassive.requestEventReportSend_Port_Load_Ready();
+			m_hsmsPassive.withVariableLock([&] {
+				if (pLoadPort != nullptr) {
+					m_hsmsPassive.setVariableValue("PortId", pLoadPort->getID());
+				}
+				m_hsmsPassive.requestEventReportSend_Port_Load_Ready();
+			});
 		}
 		else if (status == PORT_UNLOAD_READY) {
 			SERVO::CLoadPort* pLoadPort = dynamic_cast<SERVO::CLoadPort*>(pEquipment);
-			if (pLoadPort != nullptr) {
-				m_hsmsPassive.setVariableValue("UnloadReadyPortId", pLoadPort->getID());
-			}
-			m_hsmsPassive.requestEventReportSend_Port_Unload_Ready();
+			m_hsmsPassive.withVariableLock([&] {
+				if (pLoadPort != nullptr) {
+					m_hsmsPassive.setVariableValue("PortId", pLoadPort->getID());
+					if (prevStatus == PORT_INUSE) {
+						m_hsmsPassive.setVariableValue("PortId", pLoadPort->getID());
+						m_hsmsPassive.requestEventReportSend_Port_Ready_To_Release();
+					}
+				}
+				m_hsmsPassive.requestEventReportSend_Port_Unload_Ready();
+			});
+		}
+		else if (status == PORT_EMPTY) {
+			SERVO::CLoadPort* pLoadPort = dynamic_cast<SERVO::CLoadPort*>(pEquipment);
+			m_hsmsPassive.withVariableLock([&] {
+				if (pLoadPort != nullptr) {
+					m_hsmsPassive.setVariableValue("PortId", pLoadPort->getID());
+				}
+				m_hsmsPassive.requestEventReportSend_LoadPortNotAssoc();
+			});
 		}
 		notifyPtr(RX_CODE_LOADPORT_STATUS_CHANGED, pEquipment);
+	};
+	masterListener.onProcessStateChanged = [&](void* pMaster, SERVO::CEquipment* pEquipment, int slotNo, SERVO::PROCESS_STATE prevState, SERVO::PROCESS_STATE state) {
+		(void)pMaster;
+		const int eqId = pEquipment ? pEquipment->getID() : 0;
+
+		// 淇濇寔鍚屼竴閿佽寖鍥村唴锛氭洿鏂版墍闇� SV 骞朵緷娆′笂鎶ワ紝淇濊瘉 set+send 鍘熷瓙鎬�
+		m_hsmsPassive.withVariableLock([&] {
+			// Timestamp VID (Clock, VID=500) for all related reports.
+			m_hsmsPassive.setVariableValue("Clock", CToolUnits::getCurrentTimeString().c_str());
+
+			// Common payload VIDs for SubEqp/Unit
+			if (pEquipment != nullptr) {
+				m_hsmsPassive.setVariableValue("SubEqpName", pEquipment->getName().c_str());
+			}
+			m_hsmsPassive.setVariableValue("SubEqpSlot", slotNo);
+
+			// ProcessStateChanged (equipment-level): update SVs 700/701, then report CEID=700
+			m_hsmsPassive.setVariableValue("PreviousProcessState", (__int64)prevState);
+			m_hsmsPassive.setVariableValue("CurrentProcessState", (__int64)state);
+			m_hsmsPassive.requestEventReportSend("ProcessStateChanged");
+
+			// SubEqp events (per equipment, ignore slot distinction except payload)
+			static std::map<int, SERVO::PROCESS_STATE> s_prevSubEqpState;
+			const auto prevEqState = s_prevSubEqpState[eqId];
+			if (prevEqState != state) {
+				// state change
+				m_hsmsPassive.requestEventReportSend("SubEqpStateChange");
+			}
+			if (state == SERVO::PROCESS_STATE::Processing) {
+				m_hsmsPassive.requestEventReportSend_SubEqpStart();
+			}
+			else if (state == SERVO::PROCESS_STATE::Complete) {
+				m_hsmsPassive.requestEventReportSend_SubEqpEnd();
+			}
+			s_prevSubEqpState[eqId] = state;
+
+			// Unit events (per equipment slot)
+			static std::map<int, std::map<int, SERVO::PROCESS_STATE>> s_prevUnitState;
+			const auto prevUnitState = s_prevUnitState[eqId][slotNo];
+			if (prevUnitState != state) {
+				m_hsmsPassive.requestEventReportSend("UnitStateChange");
+				if (state == SERVO::PROCESS_STATE::Processing) {
+					m_hsmsPassive.requestEventReportSend("UnitStart");
+				}
+				else if (state == SERVO::PROCESS_STATE::Complete) {
+					m_hsmsPassive.requestEventReportSend("UnitEnd");
+				}
+				s_prevUnitState[eqId][slotNo] = state;
+			}
+		});
+	};
+	masterListener.onSVDataReport = [&](void* pMaster, SERVO::CEquipment* pEquipment, const std::vector<CParam>& params) {
+		(void)pMaster;
+		const int eqId = pEquipment ? pEquipment->getID() : 0;
+
+		auto sendSv = [&](const auto& vidMap, const char* evName) {
+			const size_t count = (std::min)(params.size(), vidMap.size());
+			m_hsmsPassive.withVariableLock([&] {
+				if (pEquipment != nullptr) {
+					m_hsmsPassive.setVariableValue("SubEqpName", pEquipment->getName().c_str());
+				}
+				m_hsmsPassive.setVariableValue("SubEqpSlot", 0);
+				m_hsmsPassive.setVariableValue("Clock", CToolUnits::getCurrentTimeString().c_str());
+				for (size_t idx = 0; idx < count; ++idx) {
+					const std::string val = formatParamValue(params[idx]);
+					m_hsmsPassive.setVariableValue(std::to_string(vidMap[idx]).c_str(), val.c_str());
+				}
+				m_hsmsPassive.requestEventReportSend(evName);
+			});
+		};
+
+		if (eqId == EQ_ID_Bonder1 || eqId == EQ_ID_Bonder2) {
+			static constexpr std::array<int, 19> vids = {
+				6000,6001,6002,6003,6004,6005,6006,6007,6008,6009,
+				6010,6011,6012,6013,6014,6015,6016,6017,6018
+			};
+			sendSv(vids, "BonderSVData");
+		}
+		else if (eqId == EQ_ID_VACUUMBAKE) {
+			static constexpr std::array<int, 18> vids = {
+				6200,6201,6202,6203,6204,6205,6206,6207,6208,
+				6209,6210,6211,6212,6213,6214,6215,6216,6217
+			};
+			sendSv(vids, "VacuumBakeSVData");
+		}
+		else if (eqId == EQ_ID_BAKE_COOLING) {
+			static constexpr std::array<int, 20> vids = {
+				6400,6401,6402,6403,6404,6405,6406,6407,6408,6409,
+				6410,6411,6412,6413,6414,6415,6416,6417,6418,6419
+			};
+			sendSv(vids, "BakeCoolingSVData");
+		}
+		else if (eqId == EQ_ID_MEASUREMENT) {
+			static constexpr std::array<int, 2> vids = { 6600, 6601 };
+			sendSv(vids, "MeasurementSVData");
+		}
+	};
+	masterListener.onProcessDataReport = [&](void* pMaster, SERVO::CEquipment* pEquipment, const std::vector<CParam>& params) {
+		(void)pMaster;
+		const int eqId = pEquipment ? pEquipment->getID() : 0;
+
+		auto sendProcess = [&](const auto& vidMap, const char* evName) {
+			const size_t count = (std::min)(params.size(), vidMap.size());
+			m_hsmsPassive.withVariableLock([&] {
+				if (pEquipment != nullptr) {
+					m_hsmsPassive.setVariableValue("SubEqpName", pEquipment->getName().c_str());
+				}
+				m_hsmsPassive.setVariableValue("SubEqpSlot", 0);
+				m_hsmsPassive.setVariableValue("Clock", CToolUnits::getCurrentTimeString().c_str());
+				for (size_t idx = 0; idx < count; ++idx) {
+					const std::string val = formatParamValue(params[idx]);
+					m_hsmsPassive.setVariableValue(std::to_string(vidMap[idx]).c_str(), val.c_str());
+				}
+				m_hsmsPassive.requestEventReportSend(evName);
+			});
+		};
+
+		if (eqId == EQ_ID_Bonder1 || eqId == EQ_ID_Bonder2) {
+			static constexpr std::array<int, 22> vids = {
+				6100,6101,6102,6103,6104,6105,6106,6107,6108,6109,6110,
+				6111,6112,6113,6114,6115,6116,6117,6118,6119,6120,6121
+			};
+			sendProcess(vids, "BonderProcessData");
+		}
+		else if (eqId == EQ_ID_VACUUMBAKE) {
+			static constexpr std::array<int, 5> vids = { 6300,6301,6302,6303,6304 };
+			sendProcess(vids, "VacuumBakeProcessData");
+		}
+		else if (eqId == EQ_ID_BAKE_COOLING) {
+			static constexpr std::array<int, 4> vids = { 6500,6501,6502,6503 };
+			sendProcess(vids, "BakeCoolingProcessData");
+		}
+		else if (eqId == EQ_ID_MEASUREMENT) {
+			static constexpr std::array<int, 4> vids = { 6700,6701,6702,6703 };
+			sendProcess(vids, "MeasurementProcessData");
+		}
 	};
 	masterListener.onCTRoundEnd = [&](void* pMaster, int round) {
 		m_configuration.setContinuousTransferCount(round);
 	};
 	masterListener.onCjStart = [&](void* pMaster, void* pj) {
-		m_hsmsPassive.setVariableValue("CJStartID", ((SERVO::CControlJob*)pj)->id().c_str());
-		m_hsmsPassive.requestEventReportSend_CJ_Start();
+		m_hsmsPassive.withVariableLock([&] {
+			m_hsmsPassive.setVariableValue("CJStartID", ((SERVO::CControlJob*)pj)->id().c_str());
+			m_hsmsPassive.requestEventReportSend_CJ_Start();
+		});
 	};
 	masterListener.onCjEnd = [&](void* pMaster, void* pj) {
-		m_hsmsPassive.setVariableValue("CJEndID", ((SERVO::CControlJob*)pj)->id().c_str());
-		m_hsmsPassive.requestEventReportSend_CJ_End();
+		m_hsmsPassive.withVariableLock([&] {
+			m_hsmsPassive.setVariableValue("CJEndID", ((SERVO::CControlJob*)pj)->id().c_str());
+			m_hsmsPassive.requestEventReportSend_CJ_End();
+		});
 
-		// 结批,保存ControlJob
+		// 缁撴壒锛屼繚瀛楥ontrolJob
 		// 
 	};
 	masterListener.onPjStart = [&](void* pMaster, void* pj) {
-		m_hsmsPassive.setVariableValue("PJStartID", ((SERVO::CProcessJob*)pj)->id().c_str());
-		m_hsmsPassive.requestEventReportSend_PJ_Start();
+		m_hsmsPassive.withVariableLock([&] {
+			m_hsmsPassive.setVariableValue("PJStartID", ((SERVO::CProcessJob*)pj)->id().c_str());
+			m_hsmsPassive.requestEventReportSend_PJ_Start();
+		});
 	};
 	masterListener.onPjEnd = [&](void* pMaster, void* pj) {
-		m_hsmsPassive.setVariableValue("PJEndID", ((SERVO::CProcessJob*)pj)->id().c_str());
-		m_hsmsPassive.requestEventReportSend_PJ_End();
+		m_hsmsPassive.withVariableLock([&] {
+			m_hsmsPassive.setVariableValue("PJEndID", ((SERVO::CProcessJob*)pj)->id().c_str());
+			m_hsmsPassive.requestEventReportSend_PJ_End();
+		});
 	};
 	masterListener.onPanelStart = [&](void* pMaster, void* pPanel) {
-		m_hsmsPassive.setVariableValue("PanelStartID", ((SERVO::CGlass*)pPanel)->getID().c_str());
-		m_hsmsPassive.requestEventReportSend_Panel_Start();
+		m_hsmsPassive.withVariableLock([&] {
+			m_hsmsPassive.setVariableValue("PanelStartID", ((SERVO::CGlass*)pPanel)->getID().c_str());
+			m_hsmsPassive.requestEventReportSend_Panel_Start();
+		});
 	};
 	masterListener.onPanelEnd = [&](void* pMaster, void* pPanel) {
-		m_hsmsPassive.setVariableValue("PanelEndID", ((SERVO::CGlass*)pPanel)->getID().c_str());
-		m_hsmsPassive.requestEventReportSend_Panel_End();
+		m_hsmsPassive.withVariableLock([&] {
+			m_hsmsPassive.setVariableValue("PanelEndID", ((SERVO::CGlass*)pPanel)->getID().c_str());
+			m_hsmsPassive.requestEventReportSend_Panel_End();
+			// Placeholder payload to match log shape: EV_PROCESS_DATA_REPORT can carry a single A-string (may be empty).
+			m_hsmsPassive.setVariableValue("ProcessDataReportText", "");
+			m_hsmsPassive.requestEventReportSend_ProcessDataReport();
+		});
 		auto& db = GlassLogDb::Instance();
 		db.insertFromCGlass((*(SERVO::CGlass*)pPanel));
 		SERVO::CGlass* pBuddy = ((SERVO::CGlass*)pPanel)->getBuddy();
@@ -464,33 +982,37 @@
 	m_master.setContinuousTransferCount(m_configuration.getContinuousTransferCount());
 
 
-	// master 设置缓存文件
+	// master 璁剧疆缂撳瓨鏂囦欢
 	CString strMasterDataFile;
 	strMasterDataFile.Format(_T("%s\\Master.dat"), (LPTSTR)(LPCTSTR)m_strWorkDir);
 	m_master.setCacheFilepath((LPTSTR)(LPCTSTR)strMasterDataFile);
 	m_master.setCompareMapsBeforeProceeding(m_configuration.isCompareMapsBeforeProceeding());
 	m_master.setJobMode(m_configuration.isJobMode());
 
-	// 加截Job
+	// 鍔犳埅Job
 	strMasterDataFile.Format(_T("%s\\MasterState.dat"), (LPTSTR)(LPCTSTR)m_strWorkDir);
 	std::string strPath = std::string((LPTSTR)(LPCTSTR)strMasterDataFile);
 	m_master.setStateFile(strPath);
 
 
 
-	// 加载警告信息
+	// 鍔犺浇璀﹀憡淇℃伅
 	AlarmManager& alarmManager = AlarmManager::getInstance();
 	char szBuffer[MAX_PATH];
 	sprintf_s(szBuffer, MAX_PATH, "%s\\AlarmList.csv", (LPTSTR)(LPCTSTR)m_strWorkDir);
 	alarmManager.readAlarmFile(szBuffer);
+	LOGI("[BOOT][MODEL] Alarm list loaded, cost=%llu ms",
+		(unsigned long long)(GetTickCount64() - boot_model_begin));
 
 
-	// Glass数据库
+	// Glass鏁版嵁搴�
 	strLogDir.Format(_T("%s\\db\\process.db"), (LPTSTR)(LPCTSTR)m_strWorkDir);
 	std::string path((LPTSTR)(LPCTSTR)strLogDir);
 	GlassLogDb::Init(path);
 
 
+	LOGI("[BOOT][MODEL] init finished, total cost=%llu ms",
+		(unsigned long long)(GetTickCount64() - boot_model_begin));
 	return 0;
 }
 
diff --git a/SourceCode/Bond/Servo/Model.h b/SourceCode/Bond/Servo/Model.h
index e1b1563..51a5427 100644
--- a/SourceCode/Bond/Servo/Model.h
+++ b/SourceCode/Bond/Servo/Model.h
@@ -3,6 +3,17 @@
 #include "HsmsPassive.h"
 #include "CMaster.h"
 #include "CGlassPool.h"
+#include <cstdint>
+#include <string>
+
+enum class ControlState : uint8_t {
+	OfflineEquipment = 0,
+	OfflineAttempt = 1,
+	Online = 2,
+	OfflineHost = 3,
+	OnlineLocal = 4,
+	OnlineRemote = 5,
+};
 
 class CModel
 {
@@ -20,6 +31,21 @@
 	void setPortEnable(unsigned int index, BOOL bEnable);
 	int init();
 	int term();
+
+	ControlState getControlState() const noexcept { return m_currentControlState; }
+	void setControlState(ControlState newState);
+	bool raiseSoftAlarm(int alarmId,
+		const std::string& desc = "",
+		int level = -1,
+		int deviceId = 0,
+		int unitId = 0,
+		const char* deviceName = "Software",
+		const char* unitName = "App");
+	void clearSoftAlarm(int alarmId, int deviceId = 0, int unitId = 0);
+
+private:
+	void refreshDerivedSVs();
+	void notifyControlJobChanged();
 
 public:
 	int notify(int code);
@@ -46,5 +72,7 @@
 	IObservableEmitter* m_pObservableEmitter;
 	CString m_strWorkDir;
 	CString m_strDataDir;
-};
 
+private:
+	ControlState m_currentControlState{ ControlState::OfflineEquipment };
+};
diff --git a/SourceCode/Bond/Servo/PageRecipe.cpp b/SourceCode/Bond/Servo/PageRecipe.cpp
index cf387ce..841a96f 100644
--- a/SourceCode/Bond/Servo/PageRecipe.cpp
+++ b/SourceCode/Bond/Servo/PageRecipe.cpp
@@ -138,9 +138,12 @@
 	// 閬嶅巻鏁版嵁骞舵彃鍏ュ埌CListCtrl涓�
 	for (int i = 0; i < static_cast<int>(vecRecipe.size()); ++i) {
 		const RecipeInfo& recipe = vecRecipe[i];
+		// 鍘熺▼搴忚姹侾PID鏈夊瓙閰嶆柟锛屽厛娉ㄩ噴
+		/*
 		if (recipe.vecDeviceList.empty() || recipe.vecDeviceList.size() > 6){
 			continue;
 		}
+		*/
 
 		m_listPPID.InsertItem(i, _T("")); // 绗�0鍒楃┖鐧�
 
@@ -386,13 +389,12 @@
 
 void CPageRecipe::OnBnClickedButtonNew()
 {
-	// TODO: 鍦ㄦ娣诲姞鎺т欢閫氱煡澶勭悊绋嬪簭浠g爜
-	//CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_EQUIPMENT);
-	//int nSel = pComboBox->GetCurSel();
-	//SERVO::CEquipment* pEq = (SERVO::CEquipment*)pComboBox->GetItemDataPtr(nSel);
-	//if (pEq == nullptr) {
-	//	return;
-	//}
+	int rc = UX_CanExecute(L"recipe");
+	if (rc != 1) {
+		AfxMessageBox("鎿嶄綔鏉冮檺涓嶈冻锛岃鑱旂郴绠$悊浜哄憳锛�");
+		return;
+	}
+	UX_RecordAction(L"recipe");
 
 	CRecipeDeviceBindDlg dlg(this);
 	if (dlg.DoModal() == IDOK) {
@@ -452,6 +454,13 @@
 
 void CPageRecipe::OnBnClickedButtonModify()
 {
+	int rc = UX_CanExecute(L"recipe");
+	if (rc != 1) {
+		AfxMessageBox("鎿嶄綔鏉冮檺涓嶈冻锛岃鑱旂郴绠$悊浜哄憳锛�");
+		return;
+	}
+	UX_RecordAction(L"recipe");
+
 	// TODO: 鍦ㄦ娣诲姞鎺т欢閫氱煡澶勭悊绋嬪簭浠g爜
 	CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_EQUIPMENT);
 	if (pComboBox == nullptr || !::IsWindow(pComboBox->m_hWnd)) {
@@ -492,6 +501,13 @@
 
 void CPageRecipe::OnBnClickedButtonDelete()
 {
+	int rc = UX_CanExecute(L"recipe");
+	if (rc != 1) {
+		AfxMessageBox("鎿嶄綔鏉冮檺涓嶈冻锛岃鑱旂郴绠$悊浜哄憳锛�");
+		return;
+	}
+	UX_RecordAction(L"recipe");
+
 	// TODO: 鍦ㄦ娣诲姞鎺т欢閫氱煡澶勭悊绋嬪簭浠g爜
 	POSITION pos = m_listPPID.GetFirstSelectedItemPosition();
 	if (!pos) { 
@@ -518,6 +534,13 @@
 
 void CPageRecipe::OnBnClickedButtonDeleteAll()
 {
+	int rc = UX_CanExecute(L"recipe");
+	if (rc != 1) {
+		AfxMessageBox("鎿嶄綔鏉冮檺涓嶈冻锛岃鑱旂郴绠$悊浜哄憳锛�");
+		return;
+	}
+	UX_RecordAction(L"recipe");
+
 	// TODO: 鍦ㄦ娣诲姞鎺т欢閫氱煡澶勭悊绋嬪簭浠g爜
 	if (IDYES != AfxMessageBox(_T("纭畾瑕佸垹闄ゅ叏閮ㄩ厤鏂硅褰曞悧锛�"), MB_YESNO | MB_ICONWARNING)) {
 		return;
diff --git a/SourceCode/Bond/Servo/PortConfigurationDlg.cpp b/SourceCode/Bond/Servo/PortConfigurationDlg.cpp
index 62ad3a0..27bc7a8 100644
--- a/SourceCode/Bond/Servo/PortConfigurationDlg.cpp
+++ b/SourceCode/Bond/Servo/PortConfigurationDlg.cpp
@@ -438,14 +438,68 @@
 {
     int selPort = (0 <= m_nCurSelPort && m_nCurSelPort <= 3) ? m_nCurSelPort
         : m_comboPort.GetCurSel();
-    if (selPort < 0 || selPort >= 4) return;
-    m_pPort[selPort]->sendCassetteCtrlCmd(CCC_PROCESS_START, nullptr, 0, 0, 0, nullptr, nullptr);
+    if (selPort < 0 || selPort >= 4) {
+        LOGE("ProcessStart invalid port index: %d", selPort);
+        return;
+    }
+
+    SERVO::CLoadPort* pPort = m_pPort[selPort];
+    if (pPort == nullptr) {
+        LOGE("ProcessStart port pointer is null, index: %d", selPort);
+        return;
+    }
+
+    constexpr short cmd = CCC_PROCESS_START;
+    LOGI("ProcessStart request: port=%d, cmd=%d", selPort + 1, cmd);
+    int ret = pPort->sendCassetteCtrlCmd(cmd, nullptr, 0, 0, 0, nullptr,
+        [selPort](int code) -> int {
+            if (code == WOK) {
+                LOGI("ProcessStart write complete: port=%d, code=WOK", selPort + 1);
+            }
+            else {
+                LOGE("ProcessStart write failed: port=%d, code=%d", selPort + 1, code);
+            }
+            return 0;
+        });
+    if (ret != 0) {
+        LOGE("ProcessStart sendCassetteCtrlCmd immediate failure: port=%d, ret=%d", selPort + 1, ret);
+    }
+    else {
+        LOGI("ProcessStart sendCassetteCtrlCmd dispatched: port=%d", selPort + 1);
+    }
 }
 
 void CPortConfigurationDlg::OnBnClickedButtonProcessCancel()
 {
     int selPort = (0 <= m_nCurSelPort && m_nCurSelPort <= 3) ? m_nCurSelPort
         : m_comboPort.GetCurSel();
-    if (selPort < 0 || selPort >= 4) return;
-    m_pPort[selPort]->sendCassetteCtrlCmd(CCC_PROCESS_CANCEL, nullptr, 0, 0, 0, nullptr, nullptr);
+    if (selPort < 0 || selPort >= 4) {
+        LOGE("ProcessCancel invalid port index: %d", selPort);
+        return;
+    }
+
+    SERVO::CLoadPort* pPort = m_pPort[selPort];
+    if (pPort == nullptr) {
+        LOGE("ProcessCancel port pointer is null, index: %d", selPort);
+        return;
+    }
+
+    constexpr short cmd = CCC_PROCESS_CANCEL;
+    LOGI("ProcessCancel request: port=%d, cmd=%d", selPort + 1, cmd);
+    int ret = pPort->sendCassetteCtrlCmd(cmd, nullptr, 0, 0, 0, nullptr,
+        [selPort](int code) -> int {
+            if (code == WOK) {
+                LOGI("ProcessCancel write complete: port=%d, code=WOK", selPort + 1);
+            }
+            else {
+                LOGE("ProcessCancel write failed: port=%d, code=%d", selPort + 1, code);
+            }
+            return 0;
+        });
+    if (ret != 0) {
+        LOGE("ProcessCancel sendCassetteCtrlCmd immediate failure: port=%d, ret=%d", selPort + 1, ret);
+    }
+    else {
+        LOGI("ProcessCancel sendCassetteCtrlCmd dispatched: port=%d", selPort + 1);
+    }
 }
diff --git a/SourceCode/Bond/Servo/ProcessJob.cpp b/SourceCode/Bond/Servo/ProcessJob.cpp
index 184b298..eb92df5 100644
--- a/SourceCode/Bond/Servo/ProcessJob.cpp
+++ b/SourceCode/Bond/Servo/ProcessJob.cpp
@@ -75,6 +75,11 @@
         return m_issues;
     }
 
+    void CProcessJob::addIssue(uint32_t code, const std::string& msg)
+    {
+        m_issues.push_back({ code, msg });
+    }
+
     bool CProcessJob::validate(const IResourceView& rv)
     {
         m_issues.clear();
@@ -83,10 +88,6 @@
         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");
diff --git a/SourceCode/Bond/Servo/ProcessJob.h b/SourceCode/Bond/Servo/ProcessJob.h
index bbc8926..576b30a 100644
--- a/SourceCode/Bond/Servo/ProcessJob.h
+++ b/SourceCode/Bond/Servo/ProcessJob.h
@@ -133,6 +133,7 @@
         // 返回问题清单(空=通过)
         bool validate(const IResourceView& rv);
         const std::vector<ValidationIssue>& issues() const;
+        void addIssue(uint32_t code, const std::string& msg);
 
         // —— 状态机(带守卫)——
         bool queue();           // NoState -> Queued
diff --git a/SourceCode/Bond/Servo/ProductionStats.cpp b/SourceCode/Bond/Servo/ProductionStats.cpp
new file mode 100644
index 0000000..edf6272
--- /dev/null
+++ b/SourceCode/Bond/Servo/ProductionStats.cpp
@@ -0,0 +1,438 @@
+#include "stdafx.h"
+#include "ProductionStats.h"
+
+#include <algorithm>
+#include <ctime>
+#include <iomanip>
+#include <sstream>
+#include <unordered_map>
+
+#include "Configuration.h"
+#include "Log.h"
+#include "sqlite3.h"
+
+#ifdef min
+#undef min
+#endif
+#ifdef max
+#undef max
+#endif
+
+static std::string FormatLocal(const std::chrono::system_clock::time_point& tp)
+{
+	const std::time_t tt = std::chrono::system_clock::to_time_t(tp);
+	std::tm tm{};
+	localtime_s(&tm, &tt);
+	std::ostringstream oss;
+	oss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
+	return oss.str();
+}
+
+static std::string FormatUtcIso(const std::chrono::system_clock::time_point& tp)
+{
+	const std::time_t tt = std::chrono::system_clock::to_time_t(tp);
+	std::tm tm{};
+	gmtime_s(&tm, &tt);
+	std::ostringstream oss;
+	oss << std::put_time(&tm, "%Y-%m-%dT%H:%M:%SZ");
+	return oss.str();
+}
+
+static bool TryParseLocalTime(const std::string& text, std::chrono::system_clock::time_point& outTp)
+{
+	if (text.empty()) return false;
+	std::tm tm{};
+	std::istringstream iss(text);
+	iss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
+	if (iss.fail()) return false;
+	tm.tm_isdst = -1;
+	const std::time_t tt = mktime(&tm);
+	if (tt == (time_t)-1) return false;
+	outTp = std::chrono::system_clock::from_time_t(tt);
+	return true;
+}
+
+static std::string GetExeDir()
+{
+	char path[MAX_PATH] = {};
+	GetModuleFileNameA(nullptr, path, MAX_PATH);
+	std::string exePath(path);
+	const size_t pos = exePath.find_last_of("\\/");
+	return (pos == std::string::npos) ? std::string() : exePath.substr(0, pos);
+}
+
+static bool FileExistsA(const std::string& path)
+{
+	const DWORD attr = GetFileAttributesA(path.c_str());
+	return (attr != INVALID_FILE_ATTRIBUTES) && ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0);
+}
+
+static std::string PickDbPath(const std::string& rel1, const std::string& rel2)
+{
+	const std::string base = GetExeDir();
+	const std::string p1 = base + "\\" + rel1;
+	if (FileExistsA(p1)) return p1;
+	return base + "\\" + rel2;
+}
+
+static void ComputeOutputFromProcessDb(
+	const ProductionShiftWindow& win,
+	ProductionOutputSummary& out)
+{
+	const std::string dbPath = PickDbPath("db\\process.db", "DB\\process.db");
+
+	sqlite3* db = nullptr;
+	if (sqlite3_open_v2(dbPath.c_str(), &db, SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX, nullptr) != SQLITE_OK) {
+		if (db) sqlite3_close(db);
+		return;
+	}
+
+	const char* sql =
+		"SELECT class_id, buddy_id, aoi_result, "
+		"IFNULL(strftime('%Y-%m-%d %H:%M:%S', t_start, 'localtime'), ''),"
+		"IFNULL(strftime('%Y-%m-%d %H:%M:%S', t_end,   'localtime'), '') "
+		"FROM glass_log "
+		"WHERE t_end IS NOT NULL AND t_end >= ? AND t_end < ?;";
+
+	sqlite3_stmt* stmt = nullptr;
+	if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {
+		sqlite3_close(db);
+		return;
+	}
+
+	sqlite3_bind_text(stmt, 1, win.startUtcIso.c_str(), -1, SQLITE_TRANSIENT);
+	sqlite3_bind_text(stmt, 2, win.endUtcIso.c_str(), -1, SQLITE_TRANSIENT);
+
+	struct PairAgg {
+		bool hasPass = false;
+		bool hasFail = false;
+		bool hasNo = false;
+		long long maxTaktSeconds = -1;
+	};
+	std::unordered_map<std::string, PairAgg> pairs;
+
+	for (;;) {
+		const int rc = sqlite3_step(stmt);
+		if (rc == SQLITE_ROW) {
+			const char* classId = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
+			const char* buddyId = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
+			const int aoi = sqlite3_column_int(stmt, 2);
+			const char* sStart = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3));
+			const char* sEnd = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 4));
+
+			const std::string a = classId ? classId : "";
+			const std::string b = buddyId ? buddyId : "";
+			std::string key;
+			if (!b.empty()) {
+				if (a <= b) key = a + "|" + b;
+				else key = b + "|" + a;
+			}
+			else {
+				key = a;
+			}
+
+			auto& agg = pairs[key];
+			if (aoi == 1) agg.hasPass = true;
+			else if (aoi == 2) agg.hasFail = true;
+			else agg.hasNo = true;
+
+			std::chrono::system_clock::time_point tpStart{}, tpEnd{};
+			if (TryParseLocalTime(sStart ? sStart : "", tpStart) && TryParseLocalTime(sEnd ? sEnd : "", tpEnd) && tpEnd > tpStart) {
+				const auto secs = std::chrono::duration_cast<std::chrono::seconds>(tpEnd - tpStart).count();
+				if (secs > agg.maxTaktSeconds) agg.maxTaktSeconds = secs;
+			}
+		}
+		else if (rc == SQLITE_DONE) {
+			break;
+		}
+		else {
+			break;
+		}
+	}
+
+	sqlite3_finalize(stmt);
+	sqlite3_close(db);
+
+	out.pairsTotal = static_cast<long long>(pairs.size());
+	long long sumTakt = 0;
+	long long cntTakt = 0;
+	for (const auto& kv : pairs) {
+		const auto& agg = kv.second;
+		if (agg.hasFail) out.pairsFail++;
+		else if (agg.hasPass) out.pairsPass++;
+		else out.pairsNoResult++;
+
+		if (agg.maxTaktSeconds >= 0) {
+			sumTakt += agg.maxTaktSeconds;
+			cntTakt += 1;
+		}
+	}
+	const long long denom = out.pairsPass + out.pairsFail;
+	out.yield = (denom > 0) ? (static_cast<double>(out.pairsPass) / static_cast<double>(denom)) : 0.0;
+	out.taktSamplePairs = cntTakt;
+	out.avgTaktSeconds = (cntTakt > 0) ? (static_cast<double>(sumTakt) / static_cast<double>(cntTakt)) : 0.0;
+}
+
+static void ComputeAlarmSummaryFromDb(
+	const ProductionShiftWindow& win,
+	ProductionAlarmSummary& out)
+{
+	const std::string dbPath = PickDbPath("DB\\AlarmManager.db", "DB\\AlarmManager.db");
+	sqlite3* db = nullptr;
+	if (sqlite3_open_v2(dbPath.c_str(), &db, SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX, nullptr) != SQLITE_OK) {
+		if (db) sqlite3_close(db);
+		return;
+	}
+
+	// 1) triggered within shift
+	{
+		const char* sql =
+			"SELECT COUNT(1) FROM alarms "
+			"WHERE start_time >= ? AND start_time < ?;";
+		sqlite3_stmt* stmt = nullptr;
+		if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
+			sqlite3_bind_text(stmt, 1, win.startLocal.c_str(), -1, SQLITE_TRANSIENT);
+			sqlite3_bind_text(stmt, 2, win.endLocal.c_str(), -1, SQLITE_TRANSIENT);
+			if (sqlite3_step(stmt) == SQLITE_ROW) out.alarmsTriggered = sqlite3_column_int(stmt, 0);
+			sqlite3_finalize(stmt);
+		}
+	}
+
+	// 2) overlapping (including active)
+	{
+		const char* sql =
+			"SELECT severity_level, start_time, end_time "
+			"FROM alarms "
+			"WHERE start_time < ? AND (end_time IS NULL OR end_time >= ?);";
+		sqlite3_stmt* stmt = nullptr;
+		if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
+			sqlite3_bind_text(stmt, 1, win.endLocal.c_str(), -1, SQLITE_TRANSIENT);
+			sqlite3_bind_text(stmt, 2, win.startLocal.c_str(), -1, SQLITE_TRANSIENT);
+
+			const auto now = std::chrono::system_clock::now();
+			for (;;) {
+				const int rc = sqlite3_step(stmt);
+				if (rc == SQLITE_ROW) {
+					const int severity = sqlite3_column_int(stmt, 0);
+					const char* sStart = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
+					const char* sEnd = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
+					std::chrono::system_clock::time_point aStart{};
+					if (!TryParseLocalTime(sStart ? sStart : "", aStart)) continue;
+					std::chrono::system_clock::time_point aEnd{};
+					bool hasEnd = TryParseLocalTime(sEnd ? sEnd : "", aEnd);
+					if (!hasEnd) aEnd = std::min(now, win.end);
+
+					const auto clipStart = std::max(aStart, win.start);
+					const auto clipEnd = std::min(aEnd, win.end);
+					if (clipEnd > clipStart) {
+						const auto secs = std::chrono::duration_cast<std::chrono::seconds>(clipEnd - clipStart).count();
+						out.downtimeMinutes += static_cast<double>(secs) / 60.0;
+					}
+
+					out.bySeverity[severity] += 1;
+					out.alarmsOverlapping += 1;
+				}
+				else if (rc == SQLITE_DONE) {
+					break;
+				}
+				else {
+					break;
+				}
+			}
+
+			sqlite3_finalize(stmt);
+		}
+	}
+
+	sqlite3_close(db);
+}
+
+static void ComputeTransferSummaryFromDb(
+	const ProductionShiftWindow& win,
+	ProductionTransferSummary& out)
+{
+	const std::string dbPath = PickDbPath("DB\\TransferManager.db", "DB\\TransferManager.db");
+	sqlite3* db = nullptr;
+	if (sqlite3_open_v2(dbPath.c_str(), &db, SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX, nullptr) != SQLITE_OK) {
+		if (db) sqlite3_close(db);
+		return;
+	}
+
+	const char* sql =
+		"SELECT status, create_time, end_time "
+		"FROM transfers "
+		"WHERE end_time >= ? AND end_time < ? AND end_time != '';";
+	sqlite3_stmt* stmt = nullptr;
+	if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {
+		sqlite3_close(db);
+		return;
+	}
+
+	sqlite3_bind_text(stmt, 1, win.startLocal.c_str(), -1, SQLITE_TRANSIENT);
+	sqlite3_bind_text(stmt, 2, win.endLocal.c_str(), -1, SQLITE_TRANSIENT);
+
+	long long totalSecs = 0;
+	long long cntSecs = 0;
+
+	for (;;) {
+		const int rc = sqlite3_step(stmt);
+		if (rc == SQLITE_ROW) {
+			const char* sStatus = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
+			const char* sCreate = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
+			const char* sEnd = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
+
+			const std::string status = sStatus ? sStatus : "";
+			out.byStatus[status] += 1;
+			out.transfersFinished += 1;
+
+			std::chrono::system_clock::time_point tpCreate{}, tpEnd{};
+			if (TryParseLocalTime(sCreate ? sCreate : "", tpCreate) && TryParseLocalTime(sEnd ? sEnd : "", tpEnd) && tpEnd > tpCreate) {
+				totalSecs += std::chrono::duration_cast<std::chrono::seconds>(tpEnd - tpCreate).count();
+				cntSecs += 1;
+			}
+		}
+		else if (rc == SQLITE_DONE) {
+			break;
+		}
+		else {
+			break;
+		}
+	}
+
+	sqlite3_finalize(stmt);
+	sqlite3_close(db);
+
+	out.avgCreateToEndSeconds = (cntSecs > 0) ? (static_cast<double>(totalSecs) / static_cast<double>(cntSecs)) : 0.0;
+}
+
+bool ProductionStats::GetCurrentShiftWindow(CConfiguration& config, ProductionShiftWindow& outWindow)
+{
+	int dayMin = 8 * 60;
+	int nightMin = 20 * 60;
+	config.getProductionShiftStartMinutes(dayMin, nightMin);
+
+	const auto now = std::chrono::system_clock::now();
+	const std::time_t ttNow = std::chrono::system_clock::to_time_t(now);
+	std::tm tmNow{};
+	localtime_s(&tmNow, &ttNow);
+	std::tm tmMid = tmNow;
+	tmMid.tm_hour = 0;
+	tmMid.tm_min = 0;
+	tmMid.tm_sec = 0;
+	tmMid.tm_isdst = -1;
+	const std::time_t ttMid = mktime(&tmMid);
+	if (ttMid == (time_t)-1) return false;
+
+	const auto midnight = std::chrono::system_clock::from_time_t(ttMid);
+	const auto startDayToday = midnight + std::chrono::minutes(dayMin);
+	const auto startNightToday = midnight + std::chrono::minutes(nightMin);
+
+	const auto startDay = (now >= startDayToday) ? startDayToday : (startDayToday - std::chrono::hours(24));
+	const auto startNight = (now >= startNightToday) ? startNightToday : (startNightToday - std::chrono::hours(24));
+
+	ProductionShiftType type = ProductionShiftType::Day;
+	auto start = startDay;
+	if (startNight > startDay) {
+		type = ProductionShiftType::Night;
+		start = startNight;
+	}
+
+	const int durationMin =
+		(type == ProductionShiftType::Day)
+		? ((nightMin - dayMin + 24 * 60) % (24 * 60))
+		: ((dayMin - nightMin + 24 * 60) % (24 * 60));
+	if (durationMin <= 0) return false;
+
+	outWindow.type = type;
+	outWindow.start = start;
+	outWindow.end = start + std::chrono::minutes(durationMin);
+	outWindow.startLocal = FormatLocal(outWindow.start);
+	outWindow.endLocal = FormatLocal(outWindow.end);
+	outWindow.startUtcIso = FormatUtcIso(outWindow.start);
+	outWindow.endUtcIso = FormatUtcIso(outWindow.end);
+	return true;
+}
+
+bool ProductionStats::ComputeCurrentShiftSummary(CConfiguration& config, ProductionShiftSummary& outSummary)
+{
+	ProductionShiftWindow win;
+	if (!GetCurrentShiftWindow(config, win)) return false;
+
+	outSummary = ProductionShiftSummary{};
+	outSummary.window = win;
+
+	ComputeOutputFromProcessDb(win, outSummary.output);
+	ComputeAlarmSummaryFromDb(win, outSummary.alarms);
+	ComputeTransferSummaryFromDb(win, outSummary.transfers);
+	return true;
+}
+
+void ProductionStats::LogCurrentShiftSummary(CConfiguration& config)
+{
+	ProductionShiftSummary s;
+	if (!ComputeCurrentShiftSummary(config, s)) {
+		LOGE("<ProductionStats>Failed to compute shift summary.");
+		return;
+	}
+
+	const char* shiftName = (s.window.type == ProductionShiftType::Day) ? "Day" : "Night";
+	LOGI("<ProductionStats>Shift=%s, [%s ~ %s]", shiftName, s.window.startLocal.c_str(), s.window.endLocal.c_str());
+	LOGI("<ProductionStats>Output(pairs): total=%lld, pass=%lld, fail=%lld, no_result=%lld, yield=%.2f%%",
+		s.output.pairsTotal, s.output.pairsPass, s.output.pairsFail, s.output.pairsNoResult, s.output.yield * 100.0);
+	LOGI("<ProductionStats>Takt: avg=%.1fs, samples=%lld", s.output.avgTaktSeconds, s.output.taktSamplePairs);
+	LOGI("<ProductionStats>Alarms: triggered=%d, overlapping=%d, downtime=%.1f min",
+		s.alarms.alarmsTriggered, s.alarms.alarmsOverlapping, s.alarms.downtimeMinutes);
+	if (!s.alarms.bySeverity.empty()) {
+		std::ostringstream oss;
+		oss << "<ProductionStats>AlarmsBySeverity:";
+		for (const auto& kv : s.alarms.bySeverity) oss << " L" << kv.first << "=" << kv.second;
+		LOGI("%s", oss.str().c_str());
+	}
+	LOGI("<ProductionStats>Transfers: finished=%d, avg(create->end)=%.1fs", s.transfers.transfersFinished, s.transfers.avgCreateToEndSeconds);
+	if (!s.transfers.byStatus.empty()) {
+		std::ostringstream oss;
+		oss << "<ProductionStats>TransfersByStatus:";
+		for (const auto& kv : s.transfers.byStatus) oss << " " << kv.first << "=" << kv.second;
+		LOGI("%s", oss.str().c_str());
+	}
+}
+
+bool ProductionStats::ComputeDayNightSummaries(CConfiguration& config, ProductionShiftSummary& outDay, ProductionShiftSummary& outNight)
+{
+	ProductionShiftSummary cur;
+	if (!ComputeCurrentShiftSummary(config, cur)) return false;
+
+	// Determine previous adjacent window for the other shift.
+	ProductionShiftSummary other = cur;
+	if (cur.window.type == ProductionShiftType::Day) {
+		other.window.type = ProductionShiftType::Night;
+		other.window.end = cur.window.start;
+		other.window.start = cur.window.start - (cur.window.end - cur.window.start);
+	}
+	else {
+		other.window.type = ProductionShiftType::Day;
+		other.window.end = cur.window.start;
+		other.window.start = cur.window.start - (cur.window.end - cur.window.start);
+	}
+	other.window.startLocal = FormatLocal(other.window.start);
+	other.window.endLocal = FormatLocal(other.window.end);
+	other.window.startUtcIso = FormatUtcIso(other.window.start);
+	other.window.endUtcIso = FormatUtcIso(other.window.end);
+
+	other.output = ProductionOutputSummary{};
+	other.alarms = ProductionAlarmSummary{};
+	other.transfers = ProductionTransferSummary{};
+	ComputeOutputFromProcessDb(other.window, other.output);
+	ComputeAlarmSummaryFromDb(other.window, other.alarms);
+	ComputeTransferSummaryFromDb(other.window, other.transfers);
+
+	if (cur.window.type == ProductionShiftType::Day) {
+		outDay = std::move(cur);
+		outNight = std::move(other);
+	}
+	else {
+		outNight = std::move(cur);
+		outDay = std::move(other);
+	}
+	return true;
+}
diff --git a/SourceCode/Bond/Servo/ProductionStats.h b/SourceCode/Bond/Servo/ProductionStats.h
new file mode 100644
index 0000000..c884228
--- /dev/null
+++ b/SourceCode/Bond/Servo/ProductionStats.h
@@ -0,0 +1,66 @@
+#pragma once
+
+#include <chrono>
+#include <map>
+#include <string>
+
+class CConfiguration;
+
+enum class ProductionShiftType : int {
+	Day = 1,
+	Night = 2
+};
+
+struct ProductionShiftWindow {
+	ProductionShiftType type{ ProductionShiftType::Day };
+	std::chrono::system_clock::time_point start{};
+	std::chrono::system_clock::time_point end{};
+	std::string startLocal;   // "YYYY-MM-DD HH:MM:SS"
+	std::string endLocal;     // "YYYY-MM-DD HH:MM:SS"
+	std::string startUtcIso;  // "YYYY-MM-DDTHH:MM:SSZ"
+	std::string endUtcIso;    // "YYYY-MM-DDTHH:MM:SSZ"
+};
+
+struct ProductionOutputSummary {
+	long long pairsTotal = 0;        // "瀵规暟"
+	long long pairsPass = 0;
+	long long pairsFail = 0;
+	long long pairsNoResult = 0;
+	double yield = 0.0;              // pairsPass / (pairsPass + pairsFail), 0 if denom==0
+
+	// Average takt time derived from glass_log.t_start/t_end (per pair, seconds)
+	double avgTaktSeconds = 0.0;
+	long long taktSamplePairs = 0;
+};
+
+struct ProductionAlarmSummary {
+	int alarmsTriggered = 0;         // start_time within shift window
+	int alarmsOverlapping = 0;       // overlaps shift window (including active)
+	double downtimeMinutes = 0.0;    // overlap minutes (best-effort)
+	std::map<int, int> bySeverity;   // severity_level -> count (overlapping)
+};
+
+struct ProductionTransferSummary {
+	int transfersFinished = 0;        // end_time within shift window
+	std::map<std::string, int> byStatus;
+	double avgCreateToEndSeconds = 0.0;
+};
+
+struct ProductionShiftSummary {
+	ProductionShiftWindow window;
+	ProductionOutputSummary output;
+	ProductionAlarmSummary alarms;
+	ProductionTransferSummary transfers;
+};
+
+class ProductionStats {
+public:
+	static bool GetCurrentShiftWindow(CConfiguration& config, ProductionShiftWindow& outWindow);
+	static bool ComputeCurrentShiftSummary(CConfiguration& config, ProductionShiftSummary& outSummary);
+	static void LogCurrentShiftSummary(CConfiguration& config);
+
+	// Computes "current shift" and its adjacent other shift, so UI can always show Day+Night numbers.
+	// - If current is Day: day=current day shift, night=previous night shift.
+	// - If current is Night: night=current night shift, day=previous day shift.
+	static bool ComputeDayNightSummaries(CConfiguration& config, ProductionShiftSummary& outDay, ProductionShiftSummary& outNight);
+};
diff --git a/SourceCode/Bond/Servo/Servo.cpp b/SourceCode/Bond/Servo/Servo.cpp
index 33f8657..c46246e 100644
--- a/SourceCode/Bond/Servo/Servo.cpp
+++ b/SourceCode/Bond/Servo/Servo.cpp
@@ -1,5 +1,5 @@
-
-// Servo.cpp : 定义应用程序的类行为。
+锘�
+// Servo.cpp : 瀹氫箟搴旂敤绋嬪簭鐨勭被琛屼负銆�
 //
 
 #include "stdafx.h"
@@ -17,9 +17,12 @@
 #include "MapPosWnd.h"
 #include "HmTab.h"
 #include "CControlJobManagerDlg.h"
+#include "ToolUnits.h"
+#include "CUserManager2.h"
+#include "AccordionWnd.h"
 
 
-// 声明全局变量,用于管理 GDI+ 初始化
+// 澹版槑鍏ㄥ眬鍙橀噺锛岀敤浜庣鐞� GDI+ 鍒濆鍖�
 ULONG_PTR g_diplusToken;
 GdiplusStartupInput g_diplusStartupInput;
 
@@ -35,34 +38,36 @@
 END_MESSAGE_MAP()
 
 
-// CServoApp 构造
+// CServoApp 鏋勯��
 
 CServoApp::CServoApp()
 {
-	// 支持重新启动管理器
+	// 鏀寔閲嶆柊鍚姩绠$悊鍣�
 	m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART;
 
-	// TODO: 在此处添加构造代码,
-	// 将所有重要的初始化放置在 InitInstance 中
+	// TODO: 鍦ㄦ澶勬坊鍔犳瀯閫犱唬鐮侊紝
+	// 灏嗘墍鏈夐噸瑕佺殑鍒濆鍖栨斁缃湪 InitInstance 涓�
+	m_nVersionNumber = 8;
+	m_strVersionName = _T("1.0.08");
 }
 
 
-// 唯一的一个 CServoApp 对象
+// 鍞竴鐨勪竴涓� CServoApp 瀵硅薄
 
 CServoApp theApp;
 
 
-// CServoApp 初始化
+// CServoApp 鍒濆鍖�
 
 BOOL CServoApp::InitInstance()
 {
-	// TODO: 调用 AfxInitRichEdit2() 以初始化 richedit2 库。\n"	// 如果一个运行在 Windows XP 上的应用程序清单指定要
-	// 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,
-	//则需要 InitCommonControlsEx()。  否则,将无法创建窗口。
+	// TODO: 璋冪敤 AfxInitRichEdit2() 浠ュ垵濮嬪寲 richedit2 搴撱�俓n"	// 濡傛灉涓�涓繍琛屽湪 Windows XP 涓婄殑搴旂敤绋嬪簭娓呭崟鎸囧畾瑕�
+	// 浣跨敤 ComCtl32.dll 鐗堟湰 6 鎴栨洿楂樼増鏈潵鍚敤鍙鍖栨柟寮忥紝
+	//鍒欓渶瑕� InitCommonControlsEx()銆�  鍚﹀垯锛屽皢鏃犳硶鍒涘缓绐楀彛銆�
 	INITCOMMONCONTROLSEX InitCtrls;
 	InitCtrls.dwSize = sizeof(InitCtrls);
-	// 将它设置为包括所有要在应用程序中使用的
-	// 公共控件类。
+	// 灏嗗畠璁剧疆涓哄寘鎷墍鏈夎鍦ㄥ簲鐢ㄧ▼搴忎腑浣跨敤鐨�
+	// 鍏叡鎺т欢绫汇��
 	InitCtrls.dwICC = ICC_WIN95_CLASSES;
 	InitCommonControlsEx(&InitCtrls);
 
@@ -71,24 +76,24 @@
 
 	AfxEnableControlContainer();
 
-	// 创建 shell 管理器,以防对话框包含
-	// 任何 shell 树视图控件或 shell 列表视图控件。
+	// 鍒涘缓 shell 绠$悊鍣紝浠ラ槻瀵硅瘽妗嗗寘鍚�
+	// 浠讳綍 shell 鏍戣鍥炬帶浠舵垨 shell 鍒楄〃瑙嗗浘鎺т欢銆�
 	CShellManager *pShellManager = new CShellManager;
 
-	// 激活“Windows Native”视觉管理器,以便在 MFC 控件中启用主题
+	// 婵�娲烩�淲indows Native鈥濊瑙夌鐞嗗櫒锛屼互渚垮湪 MFC 鎺т欢涓惎鐢ㄤ富棰�
 	CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows));
 
-	// 标准初始化
-	// 如果未使用这些功能并希望减小
-	// 最终可执行文件的大小,则应移除下列
-	// 不需要的特定初始化例程
-	// 更改用于存储设置的注册表项
-	// TODO: 应适当修改该字符串,
-	// 例如修改为公司或组织名
-	SetRegistryKey(_T("应用程序向导生成的本地应用程序"));
+	// 鏍囧噯鍒濆鍖�
+	// 濡傛灉鏈娇鐢ㄨ繖浜涘姛鑳藉苟甯屾湜鍑忓皬
+	// 鏈�缁堝彲鎵ц鏂囦欢鐨勫ぇ灏忥紝鍒欏簲绉婚櫎涓嬪垪
+	// 涓嶉渶瑕佺殑鐗瑰畾鍒濆鍖栦緥绋�
+	// 鏇存敼鐢ㄤ簬瀛樺偍璁剧疆鐨勬敞鍐岃〃椤�
+	// TODO: 搴旈�傚綋淇敼璇ュ瓧绗︿覆锛�
+	// 渚嬪淇敼涓哄叕鍙告垨缁勭粐鍚�
+	SetRegistryKey(_T("搴旂敤绋嬪簭鍚戝鐢熸垚鐨勬湰鍦板簲鐢ㄧ▼搴�"));
 
 
-	// 本程序文件目录
+	// 鏈▼搴忔枃浠剁洰褰�
 	TCHAR sDrive[_MAX_DRIVE];
 	TCHAR sDir[_MAX_DIR];
 	TCHAR sFilename[_MAX_FNAME], sAppFilename[_MAX_FNAME];
@@ -100,71 +105,77 @@
 	m_model.setWorkDir((LPTSTR)(LPCTSTR)m_strAppDir);
 
 
-	// 注册控件
+	// 鐢ㄦ埛鏁版嵁搴撶鐞�
+	CString strDir = m_strAppDir + _T("\\DB");
+	CUserManager2::getInstance().init((LPTSTR)(LPCTSTR)strDir);
+
+
+	// 娉ㄥ唽鎺т欢
 	CServoGraph::RegisterWndClass();
 	CVerticalLine::RegisterWndClass();
 	CHorizontalLine::RegisterWndClass();
 	CEqsGraphWnd::RegisterWndClass();
 	CMapPosWnd::RegisterWndClass();
 	CHmTab::RegisterWndClass();
+	CAccordionWnd::RegisterWndClass();
 
 
-	// 初始化Rx库
+	// 鍒濆鍖朢x搴�
 	RX_Init();
 	HSMS_Initialize();
 
 
-	// 初始化 GDI+
+	// 鍒濆鍖� GDI+
 	InitGDIPlus();
 
 
-	// 初始化 MFC RichEdit 控件
+	// 鍒濆鍖� MFC RichEdit 鎺т欢
 	AfxInitRichEdit2();
 
 
-	// 初始化报警管理器
+	// 鍒濆鍖栨姤璀︾鐞嗗櫒
 	try {
 		if (!AlarmManager::getInstance().initAlarmTable()) {
-			AfxMessageBox("初始化报警管理器失败!");
+			AfxMessageBox("鍒濆鍖栨姤璀︾鐞嗗櫒澶辫触锛�");
 			return FALSE;
 		}
 	}
 	catch (const std::exception& ex) {
 		CString errorMsg;
-		errorMsg.Format(_T("初始化报警管理器失败:%s"), CString(ex.what()));
+		errorMsg.Format(_T("鍒濆鍖栨姤璀︾鐞嗗櫒澶辫触锛�%s"), CString(ex.what()));
 		AfxMessageBox(errorMsg, MB_ICONERROR);
 		return FALSE;
 	}
 
-	// 初始化搬运记录管理库
+	// 鍒濆鍖栨惉杩愯褰曠鐞嗗簱
 	try {
 		if (!TransferManager::getInstance().initTransferTable()) {
-			AfxMessageBox("初始化搬运记录管理库设置失败!");
+			AfxMessageBox("鍒濆鍖栨惉杩愯褰曠鐞嗗簱璁剧疆澶辫触锛�");
 			return FALSE;
 		}
 	}
 	catch (const std::exception& ex) {
 		CString errorMsg;
-		errorMsg.Format(_T("初始化搬运记录管理库设置失败:%s"), CString(ex.what()));
+		errorMsg.Format(_T("鍒濆鍖栨惉杩愯褰曠鐞嗗簱璁剧疆澶辫触锛�%s"), CString(ex.what()));
 		AfxMessageBox(errorMsg, MB_ICONERROR);
 		return FALSE;
 	}
 
-	// 初始化运行日志管理库
+	// 鍒濆鍖栬繍琛屾棩蹇楃鐞嗗簱
 	try {
 		if (!SystemLogManager::getInstance().initSystemLogTable()) {
-			AfxMessageBox("初始化运行日志管理库失败!");
+			AfxMessageBox("鍒濆鍖栬繍琛屾棩蹇楃鐞嗗簱澶辫触锛�");
 			return FALSE;
 		}
 	}
 	catch (const std::exception& ex) {
 		CString errorMsg;
-		errorMsg.Format(_T("初始化运行日志管理库失败:%s"), CString(ex.what()));
+		errorMsg.Format(_T("鍒濆鍖栬繍琛屾棩蹇楃鐞嗗簱澶辫触锛�%s"), CString(ex.what()));
 		AfxMessageBox(errorMsg, MB_ICONERROR);
 		return FALSE;
 	}
 
-	// 初始化用户管理库
+	// 鍒濆鍖栫敤鎴风鐞嗗簱
 	try {
 		UserManager& userManager = UserManager::getInstance();
 #if !defined(_DEBUG)
@@ -175,21 +186,21 @@
 	}
 	catch (const std::exception& ex) {
 		CString errorMsg;
-		errorMsg.Format(_T("初始化用户管理库失败:%s"), CString(ex.what()));
+		errorMsg.Format(_T("鍒濆鍖栫敤鎴风鐞嗗簱澶辫触锛�%s"), CString(ex.what()));
 		AfxMessageBox(errorMsg, MB_ICONERROR);
 		return FALSE;
 	}
 
-	// 初始化配方管理库
+	// 鍒濆鍖栭厤鏂圭鐞嗗簱
 	try {
 		if (!RecipeManager::getInstance().initRecipeTable()) {
-			AfxMessageBox("初始化配方管理库失败!");
+			AfxMessageBox("鍒濆鍖栭厤鏂圭鐞嗗簱澶辫触锛�");
 			return FALSE;
 		}
 	}
 	catch (const std::exception& ex) {
 		CString errorMsg;
-		errorMsg.Format(_T("初始化配方管理库失败:%s"), CString(ex.what()));
+		errorMsg.Format(_T("鍒濆鍖栭厤鏂圭鐞嗗簱澶辫触锛�%s"), CString(ex.what()));
 		AfxMessageBox(errorMsg, MB_ICONERROR);
 		return FALSE;
 	}
@@ -200,28 +211,28 @@
 	INT_PTR nResponse = dlg.DoModal();
 	if (nResponse == IDOK)
 	{
-		// TODO: 在此放置处理何时用
-		//  “确定”来关闭对话框的代码
+		// TODO: 鍦ㄦ鏀剧疆澶勭悊浣曟椂鐢�
+		//  鈥滅‘瀹氣�濇潵鍏抽棴瀵硅瘽妗嗙殑浠g爜
 	}
 	else if (nResponse == IDCANCEL)
 	{
-		// TODO: 在此放置处理何时用
-		//  “取消”来关闭对话框的代码
+		// TODO: 鍦ㄦ鏀剧疆澶勭悊浣曟椂鐢�
+		//  鈥滃彇娑堚�濇潵鍏抽棴瀵硅瘽妗嗙殑浠g爜
 	}
 	else if (nResponse == -1)
 	{
-		TRACE(traceAppMsg, 0, "警告: 对话框创建失败,应用程序将意外终止。\n");
-		TRACE(traceAppMsg, 0, "警告: 如果您在对话框上使用 MFC 控件,则无法 #define _AFX_NO_MFC_CONTROLS_IN_DIALOGS。\n");
+		TRACE(traceAppMsg, 0, "璀﹀憡: 瀵硅瘽妗嗗垱寤哄け璐ワ紝搴旂敤绋嬪簭灏嗘剰澶栫粓姝€�俓n");
+		TRACE(traceAppMsg, 0, "璀﹀憡: 濡傛灉鎮ㄥ湪瀵硅瘽妗嗕笂浣跨敤 MFC 鎺т欢锛屽垯鏃犳硶 #define _AFX_NO_MFC_CONTROLS_IN_DIALOGS銆俓n");
 	}
 
-	// 删除上面创建的 shell 管理器。
+	// 鍒犻櫎涓婇潰鍒涘缓鐨� shell 绠$悊鍣ㄣ��
 	if (pShellManager != NULL)
 	{
 		delete pShellManager;
 	}
 
-	// 由于对话框已关闭,所以将返回 FALSE 以便退出应用程序,
-	//  而不是启动应用程序的消息泵。
+	// 鐢变簬瀵硅瘽妗嗗凡鍏抽棴锛屾墍浠ュ皢杩斿洖 FALSE 浠ヤ究閫�鍑哄簲鐢ㄧ▼搴忥紝
+	//  鑰屼笉鏄惎鍔ㄥ簲鐢ㄧ▼搴忕殑娑堟伅娉点��
 	return FALSE;
 }
 
@@ -231,42 +242,43 @@
 	m_model.term();
 	HSMS_Term();
 	RX_Term();
+	UX_Shutdown();
 
-	// 清理 GDI+
+	// 娓呯悊 GDI+
 	TermGDIPlus();
 
-	// 销毁报警表
+	// 閿�姣佹姤璀﹁〃
 	AlarmManager::getInstance().termAlarmTable();
 
-	// 销毁搬运记录管理库
+	// 閿�姣佹惉杩愯褰曠鐞嗗簱
 	TransferManager::getInstance().termTransferTable();
 
-	// 销毁运行日志
+	// 閿�姣佽繍琛屾棩蹇�
 	SystemLogManager::getInstance().termSystemLogTable();
 
-	// 销毁用户表
+	// 閿�姣佺敤鎴疯〃
 #if !defined(_DEBUG)
-// 清除 UserManager 的无操作检测
+// 娓呴櫎 UserManager 鐨勬棤鎿嶄綔妫�娴�
 	UserManager::getInstance().terminateIdleDetection();
 	KillTimer(1);
 #endif
 
-	// 销毁配方表
+	// 閿�姣侀厤鏂硅〃
 	RecipeManager::getInstance().termRecipeTable();
 
 	return CWinApp::ExitInstance();
 }
 
-// 初始化 GDI+
+// 鍒濆鍖� GDI+
 void CServoApp::InitGDIPlus()
 {
-	// 初始化 GDI+ 图形库
+	// 鍒濆鍖� GDI+ 鍥惧舰搴�
 	GdiplusStartup(&g_diplusToken, &g_diplusStartupInput, NULL);
 }
 
-// 清理 GDI+
+// 娓呯悊 GDI+
 void CServoApp::TermGDIPlus()
 {
-	// 清理 GDI+ 图形库
+	// 娓呯悊 GDI+ 鍥惧舰搴�
 	GdiplusShutdown(g_diplusToken);
 }
\ No newline at end of file
diff --git a/SourceCode/Bond/Servo/Servo.rc b/SourceCode/Bond/Servo/Servo.rc
index f3f35d6..d24a894 100644
--- a/SourceCode/Bond/Servo/Servo.rc
+++ b/SourceCode/Bond/Servo/Servo.rc
Binary files differ
diff --git a/SourceCode/Bond/Servo/Servo.vcxproj b/SourceCode/Bond/Servo/Servo.vcxproj
index 2e09825..f1eff4a 100644
--- a/SourceCode/Bond/Servo/Servo.vcxproj
+++ b/SourceCode/Bond/Servo/Servo.vcxproj
@@ -222,6 +222,8 @@
     <ClInclude Include="..\jsoncpp\include\json\value.h" />
     <ClInclude Include="..\jsoncpp\include\json\writer.h" />
     <ClInclude Include="..\jsoncpp\lib_json\json_batchallocator.h" />
+    <ClInclude Include="AccordionWnd.h" />
+    <ClInclude Include="AlarmPopupDlg.h" />
     <ClInclude Include="CBaseDlg.h" />
     <ClInclude Include="CCarrierSlotGrid.h" />
     <ClInclude Include="CCarrierSlotSelector.h" />
@@ -233,6 +235,7 @@
     <ClInclude Include="CControlJobManagerDlg.h" />
     <ClInclude Include="CCustomCheckBox.h" />
     <ClInclude Include="CCollectionEvent.h" />
+    <ClInclude Include="CEventEditDlg.h" />
     <ClInclude Include="CEquipmentPage3.h" />
     <ClInclude Include="CExpandableListCtrl.h" />
     <ClInclude Include="CGlassPool.h" />
@@ -240,14 +243,21 @@
     <ClInclude Include="ClientListDlg.h" />
     <ClInclude Include="CMyStatusbar.h" />
     <ClInclude Include="CPageCollectionEvent.h" />
+    <ClInclude Include="CPageCtrlState.h" />
     <ClInclude Include="CPageGlassList.h" />
     <ClInclude Include="CPageLinkSignal.h" />
+    <ClInclude Include="CPageProdOverview.h" />
     <ClInclude Include="CPageReport.h" />
     <ClInclude Include="CPageVarialbles.h" />
+    <ClInclude Include="CPageDataVarialbles.h" />
+    <ClInclude Include="CPanelProduction.h" />
+    <ClInclude Include="HmLabel.h" />
+    <ClInclude Include="ProductionStats.h" />
     <ClInclude Include="CParam.h" />
     <ClInclude Include="CCjPage1.h" />
     <ClInclude Include="CProcessDataListDlg.h" />
     <ClInclude Include="CReport.h" />
+    <ClInclude Include="CReportEditDlg.h" />
     <ClInclude Include="CRobotCmdContainerDlg.h" />
     <ClInclude Include="CRobotCmdTestDlg.h" />
     <ClInclude Include="CPagePortStatus.h" />
@@ -255,7 +265,12 @@
     <ClInclude Include="CRobotTaskDlg.h" />
     <ClInclude Include="CSVData.h" />
     <ClInclude Include="CServoUtilsTool.h" />
+    <ClInclude Include="CUserManager2.h" />
+    <ClInclude Include="CUserManager2Dlg.h" />
+    <ClInclude Include="CUserEdit2Dlg.h" />
+    <ClInclude Include="CUserXLogDlg.h" />
     <ClInclude Include="CVariable.h" />
+    <ClInclude Include="CVariableEditDlg2.h" />
     <ClInclude Include="DeviceRecipeParamDlg.h" />
     <ClInclude Include="GlassJson.h" />
     <ClInclude Include="GlassLogDb.h" />
@@ -280,6 +295,7 @@
     <ClInclude Include="InputDialog.h" />
     <ClInclude Include="JobSlotGrid.h" />
     <ClInclude Include="LoginDlg.h" />
+    <ClInclude Include="LoginDlg2.h" />
     <ClInclude Include="MsgDlg.h" />
     <ClInclude Include="PageRecipe.h" />
     <ClInclude Include="CDoubleGlass.h" />
@@ -435,6 +451,8 @@
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
     </ClCompile>
+    <ClCompile Include="AccordionWnd.cpp" />
+    <ClCompile Include="AlarmPopupDlg.cpp" />
     <ClCompile Include="CBaseDlg.cpp" />
     <ClCompile Include="CCarrierSlotGrid.cpp" />
     <ClCompile Include="CCarrierSlotSelector.cpp" />
@@ -446,6 +464,7 @@
     <ClCompile Include="CControlJobManagerDlg.cpp" />
     <ClCompile Include="CCustomCheckBox.cpp" />
     <ClCompile Include="CCollectionEvent.cpp" />
+    <ClCompile Include="CEventEditDlg.cpp" />
     <ClCompile Include="CEquipmentPage3.cpp" />
     <ClCompile Include="CExpandableListCtrl.cpp" />
     <ClCompile Include="CGlassPool.cpp" />
@@ -453,14 +472,22 @@
     <ClCompile Include="ClientListDlg.cpp" />
     <ClCompile Include="CMyStatusbar.cpp" />
     <ClCompile Include="CPageCollectionEvent.cpp" />
+    <ClCompile Include="CPageCtrlState.cpp" />
     <ClCompile Include="CPageGlassList.cpp" />
     <ClCompile Include="CPageLinkSignal.cpp" />
+    <ClCompile Include="CPageProdOverview.cpp" />
     <ClCompile Include="CPageReport.cpp" />
     <ClCompile Include="CPageVarialbles.cpp" />
+    <ClCompile Include="CPageDataVarialbles.cpp" />
+    <ClCompile Include="ConfigurationProduction.cpp" />
+    <ClCompile Include="CPanelProduction.cpp" />
+    <ClCompile Include="HmLabel.cpp" />
+    <ClCompile Include="ProductionStats.cpp" />
     <ClCompile Include="CParam.cpp" />
     <ClCompile Include="CCjPage1.cpp" />
     <ClCompile Include="CProcessDataListDlg.cpp" />
     <ClCompile Include="CReport.cpp" />
+    <ClCompile Include="CReportEditDlg.cpp" />
     <ClCompile Include="CRobotCmdContainerDlg.cpp" />
     <ClCompile Include="CRobotCmdTestDlg.cpp" />
     <ClCompile Include="CPagePortStatus.cpp" />
@@ -468,7 +495,12 @@
     <ClCompile Include="CRobotTaskDlg.cpp" />
     <ClCompile Include="CSVData.cpp" />
     <ClCompile Include="CServoUtilsTool.cpp" />
+    <ClCompile Include="CUserManager2.cpp" />
+    <ClCompile Include="CUserManager2Dlg.cpp" />
+    <ClCompile Include="CUserEdit2Dlg.cpp" />
+    <ClCompile Include="CUserXLogDlg.cpp" />
     <ClCompile Include="CVariable.cpp" />
+    <ClCompile Include="CVariableEditDlg2.cpp" />
     <ClCompile Include="DeviceRecipeParamDlg.cpp" />
     <ClCompile Include="GlassJson.cpp" />
     <ClCompile Include="GlassLogDb.cpp" />
@@ -491,6 +523,7 @@
     <ClCompile Include="InputDialog.cpp" />
     <ClCompile Include="JobSlotGrid.cpp" />
     <ClCompile Include="LoginDlg.cpp" />
+    <ClCompile Include="LoginDlg2.cpp" />
     <ClCompile Include="MsgDlg.cpp" />
     <ClCompile Include="PageRecipe.cpp" />
     <ClCompile Include="CDoubleGlass.cpp" />
@@ -641,4 +674,4 @@
     <Error Condition="!Exists('..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets'))" />
     <Error Condition="!Exists('..\packages\Microsoft.Windows.ImplementationLibrary.1.0.240803.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.ImplementationLibrary.1.0.240803.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
   </Target>
-</Project>
\ No newline at end of file
+</Project>
diff --git a/SourceCode/Bond/Servo/Servo.vcxproj.filters b/SourceCode/Bond/Servo/Servo.vcxproj.filters
index 8c439f7..09bb6e1 100644
--- a/SourceCode/Bond/Servo/Servo.vcxproj.filters
+++ b/SourceCode/Bond/Servo/Servo.vcxproj.filters
@@ -174,6 +174,7 @@
     <ClCompile Include="CReport.cpp" />
     <ClCompile Include="CVariable.cpp" />
     <ClCompile Include="CPageVarialbles.cpp" />
+    <ClCompile Include="CPageDataVarialbles.cpp" />
     <ClCompile Include="CPageReport.cpp" />
     <ClCompile Include="CPageCollectionEvent.cpp" />
     <ClCompile Include="ProcessJob.cpp" />
@@ -227,6 +228,23 @@
     <ClCompile Include="..\DAQBridge\proto\ProtocolCodec.cpp">
       <Filter>DAQBridge</Filter>
     </ClCompile>
+    <ClCompile Include="ClientListDlg.cpp" />
+    <ClCompile Include="LoginDlg2.cpp" />
+    <ClCompile Include="CUserManager2.cpp" />
+    <ClCompile Include="CUserManager2Dlg.cpp" />
+    <ClCompile Include="CUserEdit2Dlg.cpp" />
+    <ClCompile Include="CUserXLogDlg.cpp" />
+    <ClCompile Include="CVariableEditDlg2.cpp" />
+    <ClCompile Include="CEventEditDlg.cpp" />
+    <ClCompile Include="CReportEditDlg.cpp" />
+    <ClCompile Include="ConfigurationProduction.cpp" />
+    <ClCompile Include="CPanelProduction.cpp" />
+    <ClCompile Include="ProductionStats.cpp" />
+    <ClCompile Include="AccordionWnd.cpp" />
+    <ClCompile Include="CPageProdOverview.cpp" />
+    <ClCompile Include="HmLabel.cpp" />
+    <ClCompile Include="CPageCtrlState.cpp" />
+    <ClCompile Include="AlarmPopupDlg.cpp" />
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="AlarmManager.h" />
@@ -248,6 +266,7 @@
     <ClInclude Include="stdafx.h" />
     <ClInclude Include="targetver.h" />
     <ClInclude Include="TerminalDisplayDlg.h" />
+    <ClInclude Include="ProductionStats.h" />
     <ClInclude Include="SECSRuntimeManager.h" />
     <ClInclude Include="CCLinkPerformance\CCLinkIEControl.h">
       <Filter>CCLinkPerformance</Filter>
@@ -406,6 +425,7 @@
     <ClInclude Include="CReport.h" />
     <ClInclude Include="CVariable.h" />
     <ClInclude Include="CPageVarialbles.h" />
+    <ClInclude Include="CPageDataVarialbles.h" />
     <ClInclude Include="CPageReport.h" />
     <ClInclude Include="CPageCollectionEvent.h" />
     <ClInclude Include="ProcessJob.h" />
@@ -497,6 +517,21 @@
     <ClInclude Include="..\DAQBridge\DAQConfig.h">
       <Filter>DAQBridge</Filter>
     </ClInclude>
+    <ClInclude Include="ClientListDlg.h" />
+    <ClInclude Include="LoginDlg2.h" />
+    <ClInclude Include="CUserManager2.h" />
+    <ClInclude Include="CUserManager2Dlg.h" />
+    <ClInclude Include="CUserEdit2Dlg.h" />
+    <ClInclude Include="CUserXLogDlg.h" />
+    <ClInclude Include="CVariableEditDlg2.h" />
+    <ClInclude Include="CEventEditDlg.h" />
+    <ClInclude Include="CReportEditDlg.h" />
+    <ClInclude Include="CPanelProduction.h" />
+    <ClInclude Include="AccordionWnd.h" />
+    <ClInclude Include="CPageProdOverview.h" />
+    <ClInclude Include="HmLabel.h" />
+    <ClInclude Include="CPageCtrlState.h" />
+    <ClInclude Include="AlarmPopupDlg.h" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="Servo.rc" />
@@ -540,4 +575,4 @@
       <UniqueIdentifier>{885738f6-3122-4bb9-8308-46b7f692fb13}</UniqueIdentifier>
     </Filter>
   </ItemGroup>
-</Project>
\ No newline at end of file
+</Project>
diff --git a/SourceCode/Bond/Servo/Servo.vcxproj.user b/SourceCode/Bond/Servo/Servo.vcxproj.user
index 82c7903..0c03257 100644
--- a/SourceCode/Bond/Servo/Servo.vcxproj.user
+++ b/SourceCode/Bond/Servo/Servo.vcxproj.user
@@ -7,6 +7,6 @@
     <RemoteDebuggerCommand>\\DESKTOP-IODBVIQ\Servo\Debug\Servo.exe</RemoteDebuggerCommand>
     <RemoteDebuggerWorkingDirectory>\\DESKTOP-IODBVIQ\Servo\Debug\</RemoteDebuggerWorkingDirectory>
     <RemoteDebuggerServerName>DESKTOP-IODBVIQ</RemoteDebuggerServerName>
-    <DebuggerFlavor>WindowsRemoteDebugger</DebuggerFlavor>
+    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
   </PropertyGroup>
 </Project>
\ No newline at end of file
diff --git a/SourceCode/Bond/Servo/ServoDlg.cpp b/SourceCode/Bond/Servo/ServoDlg.cpp
index 5dac166..8bd8ca8 100644
--- a/SourceCode/Bond/Servo/ServoDlg.cpp
+++ b/SourceCode/Bond/Servo/ServoDlg.cpp
@@ -16,6 +16,7 @@
 #include "CRobotCmdContainerDlg.h"
 #include "CRobotCmdTestDlg.h"
 #include "LoginDlg.h"
+#include "LoginDlg2.h"
 #include "ChangePasswordDlg.h"
 #include "UserManagerDlg.h"
 #include "SystemLogManagerDlg.h"
@@ -30,6 +31,10 @@
 #include "InputDialog.h"
 #include "ClientListDlg.h"
 #include "CControlJobManagerDlg.h"
+#include "AlarmManager.h"
+#include "CUserManager2.h"
+#include "CUserManager2Dlg.h"
+#include "CUserXLogDlg.h"
 
 
 #ifdef _DEBUG
@@ -44,7 +49,7 @@
 #define TIMER_ID_UPDATE_RUMTIME			2
 
 /* Test */
-#define TIMER_ID_TEST					3
+#define TIMER_ID_LOGIN					3
 
 
 // 鐢ㄤ簬搴旂敤绋嬪簭鈥滃叧浜庘�濊彍鍗曢」鐨� CAboutDlg 瀵硅瘽妗�
@@ -89,9 +94,11 @@
 	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
 	m_crBkgnd = APPDLG_BACKGROUND_COLOR;
 	m_hbrBkgnd = nullptr;
+	m_nLeftPanelType = 2;
 	m_pTerminalDisplayDlg = nullptr;
 	m_pObserver = nullptr;
 	m_pPanelMaster = nullptr;
+	m_pPanelProduction = nullptr;
 	m_pPanelEquipment = nullptr;
 	m_pPanelAttributes = nullptr;
 	m_pPageGraph1 = nullptr;
@@ -104,6 +111,8 @@
 	m_pTopToolbar = nullptr;
 	m_pMyStatusbar = nullptr;
 	m_pRobotTaskDlg = nullptr;
+	m_pTab = nullptr;
+	m_pAlarmPopupDlg = nullptr;
 }
 
 void CServoDlg::DoDataExchange(CDataExchange* pDX)
@@ -131,12 +140,24 @@
 	ON_UPDATE_COMMAND_UI(ID_MENU_FILE_SECSTEST, &CServoDlg::OnUpdateMenuFileSecsTest)
 	ON_COMMAND(ID_MENU_PROJECT_VARIABLE_LIST, &CServoDlg::OnMenuProjectVarialbleList)
 	ON_UPDATE_COMMAND_UI(ID_MENU_PROJECT_VARIABLE_LIST, &CServoDlg::OnUpdateMenuProjectVarialbleList)
+	ON_COMMAND(ID_MENU_TEST_ALARM_ON, &CServoDlg::OnMenuTestAlarmOn)
+	ON_UPDATE_COMMAND_UI(ID_MENU_TEST_ALARM_ON, &CServoDlg::OnUpdateMenuTestAlarmOn)
+	ON_COMMAND(ID_MENU_TEST_ALARM_OFF, &CServoDlg::OnMenuTestAlarmOff)
+	ON_UPDATE_COMMAND_UI(ID_MENU_TEST_ALARM_OFF, &CServoDlg::OnUpdateMenuTestAlarmOff)
 	ON_COMMAND(ID_MENU_TEST_MESSAGE_SET, &CServoDlg::OnMenuTestMessageSet)
 	ON_UPDATE_COMMAND_UI(ID_MENU_TEST_MESSAGE_SET, &CServoDlg::OnUpdateMenuTestMessageSet)
 	ON_COMMAND(ID_MENU_TEST_MESSAGE_CLEAR, &CServoDlg::OnMenuTestMessageClear)
 	ON_UPDATE_COMMAND_UI(ID_MENU_TEST_MESSAGE_CLEAR, &CServoDlg::OnUpdateMenuTestMessageClear)
 	ON_COMMAND(ID_MENU_TOOLS_CLIENT_LIST, &CServoDlg::OnMenuToolsClientList)
 	ON_UPDATE_COMMAND_UI(ID_MENU_TOOLS_CLIENT_LIST, &CServoDlg::OnUpdateMenuToolsClientList)
+	ON_COMMAND(ID_MENU_TOOLS_CURVE_EMPTY, &CServoDlg::OnMenuToolsCurveEmptyMode)
+	ON_UPDATE_COMMAND_UI(ID_MENU_TOOLS_CURVE_EMPTY, &CServoDlg::OnUpdateMenuToolsCurveEmptyMode)
+	ON_COMMAND(ID_MENU_TOOLS_CURVE_PRODUCTION, &CServoDlg::OnMenuToolsCurveProductionMode)
+	ON_UPDATE_COMMAND_UI(ID_MENU_TOOLS_CURVE_PRODUCTION, &CServoDlg::OnUpdateMenuToolsCurveProductionMode)
+	ON_COMMAND(ID_MENU_WND_TEST_PANEL, &CServoDlg::OnMenuWndTestPanel)
+	ON_UPDATE_COMMAND_UI(ID_MENU_WND_TEST_PANEL, &CServoDlg::OnUpdateMenuWndTestPanel)
+	ON_COMMAND(ID_MENU_WND_PRO_PANEL, &CServoDlg::OnMenuWndProPanel)
+	ON_UPDATE_COMMAND_UI(ID_MENU_WND_PRO_PANEL, &CServoDlg::OnUpdateMenuWndProPanel)
 	ON_COMMAND(ID_MENU_HELP_ABOUT, &CServoDlg::OnMenuHelpAbout)
 	ON_WM_INITMENUPOPUP()
 	ON_WM_TIMER()
@@ -174,7 +195,7 @@
 					ASSERT(m_pPanelAttributes);
 					m_pPanelEquipment->loadDataFromEquipment(pEquipment);
 					m_pPanelAttributes->ShowWindow(SW_HIDE);
-					if (!m_pPanelEquipment->IsWindowVisible()) {
+					if (!m_pPanelEquipment->IsWindowVisible() && m_nLeftPanelType == 1) {
 						m_pPanelEquipment->ShowWindow(SW_SHOW);
 						Resize();
 					}
@@ -197,7 +218,7 @@
 			else if (RX_CODE_MASTER_STATE_CHANGED == code) {
 				SERVO::MASTERSTATE state = theApp.m_model.getMaster().getState();
 				if (state == SERVO::MASTERSTATE::READY) {
-					m_pTopToolbar->GetBtn(IDC_BUTTON_RUN)->EnableWindow(TRUE);
+					m_pTopToolbar ->GetBtn(IDC_BUTTON_RUN)->EnableWindow(TRUE);
 					m_pTopToolbar->GetBtn(IDC_BUTTON_RUN_BATCH)->EnableWindow(TRUE);
 					m_pTopToolbar->GetBtn(IDC_BUTTON_RUN_CT)->EnableWindow(TRUE);
 					m_pTopToolbar->GetBtn(IDC_BUTTON_STOP)->EnableWindow(FALSE);
@@ -249,6 +270,27 @@
 					SetTimer(TIMER_ID_UPDATE_RUMTIME, 500, nullptr);
 				}
 			}
+			else if (RX_CODE_CONTROLJOB_CHANGED == code) {
+				auto* cj = theApp.m_model.getMaster().getControlJob();
+				CString text;
+				if (cj != nullptr) {
+					std::string st = cj->getStateText();
+					text.Format(_T("ControlJob: %s (%s)"), cj->id().c_str(), st.c_str());
+					if (cj->state() == SERVO::CJState::Paused) {
+						text += _T(" [Paused]");
+					}
+				}
+				else {
+					text = _T("ControlJob: None");
+				}
+				if (m_pMyStatusbar != nullptr) {
+					m_pMyStatusbar->setJobText((LPTSTR)(LPCTSTR)text);
+					if (cj != nullptr && cj->state() == SERVO::CJState::Paused) {
+						m_pMyStatusbar->setBackgroundColor(STATUSBAR_BK_ALARM);
+						m_pMyStatusbar->setForegroundColor(RGB(0, 0, 0));
+					}
+				}
+			}
 			else if (RX_CODE_EQ_ROBOT_TASK == code) {
 				int exCode;
 				if (pAny->getIntValue("exCode", exCode)) {
@@ -295,6 +337,9 @@
 					//dlg.DoModal();
 				}
 			}
+			else if (RX_CODE_ALARM_SET == code || RX_CODE_ALARM_CLEAR == code) {
+				RefreshAlarmBadge();
+			}
 
 			if (RX_CODE_PASSIVE_STATUS_CHANGED == code) {
 				int state = 0;
@@ -302,18 +347,18 @@
 
 				if (STATE::NOT_CONNECTED == state) {
 					m_pMyStatusbar->setCimBtnText("Disconnected");
-					//m_labelPassiveState.setBackground(DISCONNECTED_BACKGROUND);
-					//m_labelPassiveState.setForeground(DISCONNECTED_FOREGROUND, TRUE);
+					m_pMyStatusbar->setCimBtnColors(
+						CIM_STATUS_BK_DISCONNECTED, CIM_STATUS_BK_DISCONNECTED, RGB(0, 0, 0));
 				}
 				else if (STATE::NOT_SELECTED == state) {
 					m_pMyStatusbar->setCimBtnText("Not Selected");
-					//m_labelPassiveState.setBackground(NOT_SELECTED_BACKGROUND);
-					//m_labelPassiveState.setForeground(NOT_SELECTED_FOREGROUND, TRUE);
+					m_pMyStatusbar->setCimBtnColors(
+						CIM_STATUS_BK_DISCONNECTED, CIM_STATUS_BK_DISCONNECTED, RGB(0, 0, 0));
 				}
 				else if (STATE::SELECTED == state) {
 					m_pMyStatusbar->setCimBtnText("Selected");
-					//m_labelPassiveState.setBackground(SELECTED_BACKGROUND);
-					//m_labelPassiveState.setForeground(SELECTED_FOREGROUND, TRUE);
+					m_pMyStatusbar->setCimBtnColors(
+						CIM_STATUS_BK_SELECTED, CIM_STATUS_BK_SELECTED, RGB(0, 0, 0));
 				}
 			}
 			pAny->release();
@@ -329,9 +374,88 @@
 	}
 }
 
+void CServoDlg::RefreshAlarmBadge()
+{
+	if (m_pTopToolbar == nullptr) return;
+	auto activeAlarms = AlarmManager::getInstance().getActiveAlarms();
+
+	// 缁存姢鏈鍒楄〃锛氬綋鍓嶆椿璺冧笖鏈湪宸茶闆嗗悎涓殑鎶ヨ
+	std::unordered_set<int> activeIds;
+	m_unreadAlarms.clear();
+	for (const auto& alarm : activeAlarms) {
+		activeIds.insert(alarm.nId);
+		if (m_ackAlarms.find(alarm.nId) == m_ackAlarms.end()) {
+			m_unreadAlarms.push_back(alarm);
+		}
+	}
+	// 绉婚櫎宸茶闆嗗悎涓凡涓嶅啀娲昏穬鐨勫憡璀�
+	for (auto it = m_ackAlarms.begin(); it != m_ackAlarms.end(); ) {
+		if (activeIds.find(*it) == activeIds.end()) {
+			it = m_ackAlarms.erase(it);
+		}
+		else {
+			++it;
+		}
+	}
+
+	int count = static_cast<int>(m_unreadAlarms.size());
+
+	auto* pBtn = dynamic_cast<CBlButton*>(m_pTopToolbar->GetBtn(IDC_BUTTON_ALARM));
+	if (pBtn != nullptr) {
+		if (count <= 0) {
+			pBtn->SetBadgeNumber(0);
+			pBtn->ShowDotBadge(FALSE, RGB(255, 0, 0));
+			pBtn->StopFlash();
+		}
+		else if (count <= 9) {
+			pBtn->ShowDotBadge(FALSE, RGB(255, 0, 0));
+			pBtn->SetBadgeNumber(count);
+			if (!pBtn->IsFlash()) pBtn->Flash(600);
+		}
+		else {
+			pBtn->SetBadgeNumber(0);
+			pBtn->ShowDotBadge(TRUE, RGB(255, 0, 0));
+			if (!pBtn->IsFlash()) pBtn->Flash(600);
+		}
+		pBtn->EnableWindow(TRUE);
+	}
+}
+
+void CServoDlg::AckAlarm(int alarmId)
+{
+	m_ackAlarms.insert(alarmId);
+	RefreshAlarmBadge();
+}
+
+void CServoDlg::RaiseTestAlarm()
+{
+	theApp.m_model.raiseSoftAlarm(ALID_SOFTWARE_TEST_ALARM, "Test Alarm (Ctrl+Alt+T)");
+}
+
+void CServoDlg::ClearTestAlarm()
+{
+	theApp.m_model.clearSoftAlarm(ALID_SOFTWARE_TEST_ALARM);
+}
+
+void CServoDlg::MarkAlarmsRead()
+{
+	auto* pBtn = dynamic_cast<CBlButton*>(m_pTopToolbar ? m_pTopToolbar->GetBtn(IDC_BUTTON_ALARM) : nullptr);
+	for (const auto& alarm : m_unreadAlarms) {
+		m_ackAlarms.insert(alarm.nId);
+	}
+	m_unreadAlarms.clear();
+	if (pBtn != nullptr) {
+		pBtn->SetBadgeNumber(0);
+		pBtn->ShowDotBadge(FALSE, RGB(255, 0, 0));
+		pBtn->StopFlash();
+	}
+}
+
 BOOL CServoDlg::OnInitDialog()
 {
 	CDialogEx::OnInitDialog();
+	const ULONGLONG boot_ui_begin = GetTickCount64();
+	CWaitCursor wait; // 鏁翠釜鍒濆鍖栨湡鏄剧ず绛夊緟鍏夋爣锛岄伩鍏嶇敤鎴疯浠ヤ负鍗℃
 
 	// 灏嗏�滃叧浜�...鈥濊彍鍗曢」娣诲姞鍒扮郴缁熻彍鍗曚腑銆�
 
@@ -359,14 +483,27 @@
 	SetIcon(m_hIcon, FALSE);		// 璁剧疆灏忓浘鏍�
 
 
+	// 鏈�澶у寲/绐楀彛鏍囬/鐗堟湰鍙�
+	ShowWindow(SW_MAXIMIZE);
+	CString strTitle;
+	strTitle.Format(_T("Bond Master -- V%s(%d)"), theApp.m_strVersionName, theApp.m_nVersionNumber);
+	SetWindowText(strTitle);
+
+
 	// model init
+	const ULONGLONG boot_model_begin = GetTickCount64();
 	theApp.m_model.init();
-	SetTimer(TIMER_ID_TEST, 1000, nullptr);
+	LOGI("[BOOT][UI] m_model.init finished, cost=%llu ms (since OnInit start %llu ms)",
+		(unsigned long long)(GetTickCount64() - boot_model_begin),
+		(unsigned long long)(GetTickCount64() - boot_ui_begin));
+	SetTimer(TIMER_ID_LOGIN, 1500, nullptr);
+	LOGI("[BOOT][UI] after model.init, elapsed=%llu ms", (unsigned long long)(GetTickCount64() - boot_ui_begin));
 
 	// 鑿滃崟
 	CMenu menu;
 	menu.LoadMenu(IDR_MENU_APP);
 	SetMenu(&menu);
+	LOGI("[BOOT][UI] menu loaded, elapsed=%llu ms", (unsigned long long)(GetTickCount64() - boot_ui_begin));
 
 
 	// toolbar
@@ -379,25 +516,48 @@
 	ASSERT(hMenu);
 	::EnableMenuItem(hMenu, ID_OPEATOR_SWITCH, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
 	m_pTopToolbar->GetBtn(IDC_BUTTON_JOBS)->EnableWindow(TRUE);
+	LOGI("[BOOT][UI] toolbar created, elapsed=%llu ms", (unsigned long long)(GetTickCount64() - boot_ui_begin));
 
 
 	// Tab
+	const ULONGLONG boot_pages_begin = GetTickCount64();
 	m_pPageGraph1 = new CPageGraph1();
 	m_pPageGraph1->Create(IDD_PAGE_GRAPH1, this);
+	LOGI("[BOOT][UI] page Graph1 created, cost=%llu ms (elapsed=%llu ms)",
+		(unsigned long long)(GetTickCount64() - boot_pages_begin),
+		(unsigned long long)(GetTickCount64() - boot_ui_begin));
 	m_pPageGraph2 = new CPageGraph2();
 	m_pPageGraph2->Create(IDD_PAGE_GRAPH2, this);
+	LOGI("[BOOT][UI] page Graph2 created, cost=%llu ms (elapsed=%llu ms)",
+		(unsigned long long)(GetTickCount64() - boot_pages_begin),
+		(unsigned long long)(GetTickCount64() - boot_ui_begin));
 	m_pPageGlassList = new CPageGlassList();
 	m_pPageGlassList->Create(IDD_PAGE_GLASS_LIST, this);
+	LOGI("[BOOT][UI] page GlassList created, cost=%llu ms (elapsed=%llu ms)",
+		(unsigned long long)(GetTickCount64() - boot_pages_begin),
+		(unsigned long long)(GetTickCount64() - boot_ui_begin));
 	m_pPageRecipe = new CPageRecipe();
 	m_pPageRecipe->Create(IDD_PAGE_RECIPE, this);
+	LOGI("[BOOT][UI] page Recipe created, cost=%llu ms (elapsed=%llu ms)",
+		(unsigned long long)(GetTickCount64() - boot_pages_begin),
+		(unsigned long long)(GetTickCount64() - boot_ui_begin));
 	m_pPageAlarm = new CPageAlarm();
 	m_pPageAlarm->Create(IDD_DIALOG_ALARM, this);
+	LOGI("[BOOT][UI] page Alarm created, cost=%llu ms (elapsed=%llu ms)",
+		(unsigned long long)(GetTickCount64() - boot_pages_begin),
+		(unsigned long long)(GetTickCount64() - boot_ui_begin));
 	m_pPageLog = new CPageLog();
 	m_pPageLog->Create(IDD_DIALOG_LOG, this);
+	LOGI("[BOOT][UI] page Log created, cost=%llu ms (elapsed=%llu ms)",
+		(unsigned long long)(GetTickCount64() - boot_pages_begin),
+		(unsigned long long)(GetTickCount64() - boot_ui_begin));
 	m_pPageTransferLog = new CPageTransferLog();
 	m_pPageTransferLog->Create(IDD_PAGE_TRANSFER_LOG, this);
+	LOGI("[BOOT][UI] page TransferLog created, cost=%llu ms (elapsed=%llu ms)",
+		(unsigned long long)(GetTickCount64() - boot_pages_begin),
+		(unsigned long long)(GetTickCount64() - boot_ui_begin));
 
-	CHmTab* m_pTab = CHmTab::Hook(GetDlgItem(IDC_TAB1)->m_hWnd);
+	m_pTab = CHmTab::Hook(GetDlgItem(IDC_TAB1)->m_hWnd);
 	m_pTab->SetPaddingLeft(20);
 	m_pTab->SetItemMarginLeft(18);
 	m_pTab->AddItem("鐘舵�佸浘", FALSE);
@@ -410,21 +570,34 @@
 	m_pTab->SetCurSel(0);
 	m_pTab->SetBkgndColor(RGB(222, 222, 222));
 	ShowChildPage(0);
+	LOGI("[BOOT][UI] pages/tabs created, elapsed=%llu ms", (unsigned long long)(GetTickCount64() - boot_ui_begin));
 
+	// 璇诲彇闈㈡澘瀹�
+	CString strIniFile;
+	strIniFile.Format(_T("%s\\%s.ini"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir, (LPTSTR)(LPCTSTR)theApp.m_strAppFile);
+	int nPanelWidth = GetPrivateProfileInt(_T("App"), _T("MasterPanelWidth"),
+		int((double)GetSystemMetrics(SM_CXSCREEN) * 0.25), (LPTSTR)(LPCTSTR)strIniFile);
 
 	m_pPanelMaster = new CPanelMaster();
+	m_pPanelMaster->setPanelWidth(nPanelWidth);
 	m_pPanelMaster->Create(IDD_PANEL_MASTER, this);
-	m_pPanelMaster->ShowWindow(SW_SHOW);
+	m_pPanelProduction = new CPanelProduction();
+	m_pPanelProduction->setPanelWidth(nPanelWidth);
+	m_pPanelProduction->Create(IDD_PANEL_PRODUCTION, this);
+	SetLeftPanelType(m_nLeftPanelType, false);
 	m_pPanelEquipment = new CPanelEquipment();
 	m_pPanelEquipment->Create(IDD_PANEL_EQUIPMENT, this);
 	m_pPanelAttributes = new CPanelAttributes();
 	m_pPanelAttributes->Create(IDD_PANEL_ATTRIBUTES, this);
 	
+	LOGI("[BOOT][UI] panels created, elapsed=%llu ms", (unsigned long long)(GetTickCount64() - boot_ui_begin));
 
 	// statusbar
 	m_pMyStatusbar = new CMyStatusbar();
 	m_pMyStatusbar->Create(IDD_STATUSBAR, this);
 	m_pMyStatusbar->ShowWindow(SW_SHOW);
+	m_pMyStatusbar->setJobText("ControlJob: None");
+	LOGI("[BOOT][UI] statusbar created, elapsed=%llu ms", (unsigned long long)(GetTickCount64() - boot_ui_begin));
 
 
 
@@ -441,10 +614,30 @@
 	InitRxWindows();
 	Resize();
 
+	// 鍔犺浇鍘嗗彶缂撳瓨鎻愮ず
+	{
+		CWaitCursor wait;
+		if (m_pMyStatusbar != nullptr) {
+			m_pMyStatusbar->setRunTimeText(_T("姝e湪鍔犺浇鍘嗗彶缂撳瓨..."));
+			m_pMyStatusbar->UpdateWindow();
+		}
+		LOGI("[BOOT][UI] before master.init, elapsed=%llu ms", (unsigned long long)(GetTickCount64() - boot_ui_begin));
 
-	// 鐩稿綋浜庡欢鏃惰皟鐢╩aster鐨勫垵濮嬪寲
-	theApp.m_model.m_master.init();
-	theApp.m_model.loadPortParams();
+		// 鐩稿綋浜庡欢鏃惰皟鐢╩aster鐨勫垵濮嬪寲
+		const ULONGLONG boot_master_begin = GetTickCount64();
+		theApp.m_model.m_master.init();
+		LOGI("[BOOT][UI] m_master.init finished, cost=%llu ms (since OnInit start %llu ms)",
+			(unsigned long long)(GetTickCount64() - boot_master_begin),
+			(unsigned long long)(GetTickCount64() - boot_ui_begin));
+		theApp.m_model.loadPortParams();
+	}
+
+	// 杩樺師鐘舵�佹爮杩愯鏃堕棿鏄剧ず锛堥伩鍏嶄竴鐩村仠鐣欏湪鈥滄鍦ㄥ姞杞藉巻鍙茬紦瀛�...鈥濓級
+	if (m_pMyStatusbar != nullptr) {
+		CString strText;
+		GetRuntimeFormatText(strText, "");
+		m_pMyStatusbar->setRunTimeText((LPTSTR)(LPCTSTR)strText);
+	}
 
 
 	// 鍒濆鍖杕aster浠ュ悗闇�瑕佹帶浠剁粦瀹氭暟鎹�
@@ -453,9 +646,14 @@
 
 	// 鏇存柊鐧诲綍鐘舵��
 	UpdateLoginStatus();
+	// 鍒濆鍖栨姤璀﹁鏍�
+	RefreshAlarmBadge();
 	//SystemLogManager::getInstance.log(SystemLogManager::LogType::Info, _T("BondEq鍚姩..."));
 	//SystemLogManager::getInstance.
 
+
+	LOGI("[BOOT][UI] OnInitDialog finished, total cost=%llu ms",
+		(unsigned long long)(GetTickCount64() - boot_ui_begin));
 
 	return TRUE;  // 闄ら潪灏嗙劍鐐硅缃埌鎺т欢锛屽惁鍒欒繑鍥� TRUE
 }
@@ -627,10 +825,34 @@
 	pPage3->Create(IDD_PAGE_VARIABLE);
 	dlg.addPage(pPage3, "Variable");
 
+	CPageDataVarialbles* pPage4 = new CPageDataVarialbles();
+	pPage4->Create(IDD_PAGE_VARIABLE);
+	dlg.addPage(pPage4, "DataVariable");
+
 	dlg.DoModal();
 }
 
 void CServoDlg::OnUpdateMenuProjectVarialbleList(CCmdUI* pCmdUI)
+{
+	pCmdUI->Enable(TRUE);
+}
+
+void CServoDlg::OnMenuTestAlarmOn()
+{
+	RaiseTestAlarm();
+}
+
+void CServoDlg::OnUpdateMenuTestAlarmOn(CCmdUI* pCmdUI)
+{
+	pCmdUI->Enable(TRUE);
+}
+
+void CServoDlg::OnMenuTestAlarmOff()
+{
+	ClearTestAlarm();
+}
+
+void CServoDlg::OnUpdateMenuTestAlarmOff(CCmdUI* pCmdUI)
 {
 	pCmdUI->Enable(TRUE);
 }
@@ -672,6 +894,50 @@
 void CServoDlg::OnUpdateMenuToolsClientList(CCmdUI* pCmdUI)
 {
 	pCmdUI->Enable(TRUE);
+}
+
+void CServoDlg::OnMenuToolsCurveEmptyMode()
+{
+	theApp.m_model.getMaster().setCurveMode(SERVO::CurveMode::EmptyChamber);
+}
+
+void CServoDlg::OnUpdateMenuToolsCurveEmptyMode(CCmdUI* pCmdUI)
+{
+	pCmdUI->Enable(TRUE);
+	pCmdUI->SetCheck(theApp.m_model.getMaster().getCurveMode() == SERVO::CurveMode::EmptyChamber);
+}
+
+void CServoDlg::OnMenuToolsCurveProductionMode()
+{
+	theApp.m_model.getMaster().setCurveMode(SERVO::CurveMode::Production);
+}
+
+void CServoDlg::OnUpdateMenuToolsCurveProductionMode(CCmdUI* pCmdUI)
+{
+	pCmdUI->Enable(TRUE);
+	pCmdUI->SetCheck(theApp.m_model.getMaster().getCurveMode() == SERVO::CurveMode::Production);
+}
+
+void CServoDlg::OnMenuWndTestPanel()
+{
+	SetLeftPanelType(1);
+}
+
+void CServoDlg::OnUpdateMenuWndTestPanel(CCmdUI* pCmdUI)
+{
+	pCmdUI->Enable(TRUE);
+	pCmdUI->SetCheck(m_nLeftPanelType == 1);
+}
+
+void CServoDlg::OnMenuWndProPanel()
+{
+	SetLeftPanelType(2);
+}
+
+void CServoDlg::OnUpdateMenuWndProPanel(CCmdUI* pCmdUI)
+{
+	pCmdUI->Enable(TRUE);
+	pCmdUI->SetCheck(m_nLeftPanelType == 2);
 }
 
 void CServoDlg::OnMenuHelpAbout()
@@ -727,6 +993,12 @@
 		m_pMyStatusbar = nullptr;
 	}
 
+	if (m_pAlarmPopupDlg != nullptr) {
+		m_pAlarmPopupDlg->DestroyWindow();
+		delete m_pAlarmPopupDlg;
+		m_pAlarmPopupDlg = nullptr;
+	}
+
 	if (m_pRobotTaskDlg != nullptr) {
 		m_pRobotTaskDlg->DestroyWindow();
 		delete m_pRobotTaskDlg;
@@ -743,6 +1015,12 @@
 		m_pPanelMaster->DestroyWindow();
 		delete m_pPanelMaster;
 		m_pPanelMaster = nullptr;
+	}
+
+	if (m_pPanelProduction != nullptr) {
+		m_pPanelProduction->DestroyWindow();
+		delete m_pPanelProduction;
+		m_pPanelProduction = nullptr;
 	}
 
 	if (m_pPanelEquipment != nullptr) {
@@ -845,9 +1123,16 @@
 	
 
 	int nPanelWidth = 0;
-	if (m_pPanelMaster != nullptr) {
+	if (m_pPanelMaster != nullptr && ::IsWindow(m_pPanelMaster->GetSafeHwnd())
+		&& m_pPanelMaster->IsWindowVisible()) {
 		nPanelWidth = m_pPanelMaster->getPanelWidth();
 		m_pPanelMaster->MoveWindow(x, y, nPanelWidth, y2 - y);
+		x += nPanelWidth;
+	}
+
+	if (m_pPanelProduction != nullptr && m_pPanelProduction->IsWindowVisible()) {
+		nPanelWidth = m_pPanelProduction->getPanelWidth();
+		m_pPanelProduction->MoveWindow(x, y, nPanelWidth, y2 - y);
 		x += nPanelWidth;
 	}
 
@@ -880,6 +1165,32 @@
 
 
 	m_pMyStatusbar->MoveWindow(0, y2, rcClient.Width(), STATUSBAR_HEIGHT);
+}
+
+void CServoDlg::SetLeftPanelType(int type, bool resize)
+{
+	if (type != 1 && type != 2) {
+		type = 1;
+	}
+
+	m_nLeftPanelType = type;
+	if (m_pPanelMaster != nullptr) {
+		m_pPanelMaster->ShowWindow(SW_HIDE);
+	}
+	if (m_pPanelProduction != nullptr) {
+		m_pPanelProduction->ShowWindow(SW_HIDE);
+	}
+	if (type == 1 && m_pPanelMaster != nullptr) {
+		m_pPanelMaster->ShowWindow(SW_SHOW);
+	}
+	else if (type == 2 && m_pPanelProduction != nullptr) {
+		m_pPanelProduction->ShowWindow(SW_SHOW);
+	}
+
+	if (resize && ::IsWindow(m_hWnd)) {
+		Resize();
+		DrawMenuBar();
+	}
 }
 
 void CServoDlg::OnClose()
@@ -928,11 +1239,23 @@
 		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);
+	else if(TIMER_ID_LOGIN == nIDEvent){
+		KillTimer(TIMER_ID_LOGIN);
+		if (!CUserManager2::getInstance().isLoggedIn()) {
+			CLoginDlg2 dlg;
+			if (dlg.DoModal() != IDOK) {
+				PostMessage(WM_CLOSE);
+			}
+			else {
+				bool bRet = CUserManager2::getInstance().login((LPTSTR)(LPCTSTR)dlg.m_strUsername,
+					(LPTSTR)(LPCTSTR)dlg.m_strPassword);
+				if (!bRet) {
+					AfxMessageBox("鐧诲綍澶辫触锛岃妫�鏌ョ敤鎴峰悕鎴栧瘑鐮佹槸鍚︽纭紒");
+					PostMessage(WM_CLOSE);
+				}
+				UpdateLoginStatus();
+			}
+		}
 	}
 
 
@@ -942,7 +1265,20 @@
 LRESULT CServoDlg::OnPanelResize(WPARAM wParam, LPARAM lParam)
 {
 	int width = (int)wParam;
-	// m_pPanel->SetPanelWidth(width);
+
+	if (m_pPanelMaster != nullptr) {
+		m_pPanelMaster->setPanelWidth(width);
+	}
+	if (m_pPanelProduction != nullptr) {
+		m_pPanelProduction->setPanelWidth(width);
+	}
+
+	CString strIniFile, strValue;
+	strIniFile.Format(_T("%s\\%s.ini"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir, (LPTSTR)(LPCTSTR)theApp.m_strAppFile);
+	strValue.Format(_T("%d"), width);
+	WritePrivateProfileString(_T("App"), _T("MasterPanelWidth"),
+		(LPTSTR)(LPCTSTR)strValue, (LPTSTR)(LPCTSTR)strIniFile);
+
 	Resize();
 
 	return 0;
@@ -970,32 +1306,24 @@
 void CServoDlg::UpdateLoginStatus()
 {
 	HMENU hMenu = m_pTopToolbar->GetOperatorMenu();
-	UserManager& userManager = UserManager::getInstance();
-	if (userManager.isLoggedIn())
-	{
-		::EnableMenuItem(hMenu, ID_OPEATOR_LOGIN, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
-		::EnableMenuItem(hMenu, ID_OPERATOR_CHANGE_PASSWORD, MF_BYCOMMAND | MF_ENABLED);
+	CUserManager2& userManager = CUserManager2::getInstance();
+	if (userManager.isLoggedIn()) {
 		::EnableMenuItem(hMenu, ID_OPERATOR_SYSTEM_LOG, MF_BYCOMMAND | MF_ENABLED);
 		::EnableMenuItem(hMenu, ID_OPEATOR_SWITCH, MF_BYCOMMAND | MF_ENABLED);
-		::EnableMenuItem(hMenu, ID_OPERATOR_LOGOUT, MF_BYCOMMAND | MF_ENABLED);
 
-		if (userManager.getCurrentUserRole() == UserRole::SuperAdmin) {
+		if (userManager.IsAdminCurrent()) {
 			::EnableMenuItem(hMenu, ID_OPEATOR_USER_MANAGER, MF_BYCOMMAND | MF_ENABLED);
 		}
 		else {
 			::EnableMenuItem(hMenu, ID_OPEATOR_USER_MANAGER, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
 		}
 
-		m_pTopToolbar->SetOperatorBtnText(userManager.getCurrentUser().c_str());
+		m_pTopToolbar->SetOperatorBtnText(userManager.getCurrentUserName().c_str());
 	}
 	else {
-		::EnableMenuItem(hMenu, ID_OPEATOR_LOGIN, MF_BYCOMMAND | MF_ENABLED);
-		::EnableMenuItem(hMenu, ID_OPERATOR_CHANGE_PASSWORD, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
 		::EnableMenuItem(hMenu, ID_OPEATOR_USER_MANAGER, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
 		::EnableMenuItem(hMenu, ID_OPERATOR_SYSTEM_LOG, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
 		::EnableMenuItem(hMenu, ID_OPEATOR_SWITCH, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
-		::EnableMenuItem(hMenu, ID_OPERATOR_LOGOUT, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
-
 		m_pTopToolbar->SetOperatorBtnText(_T("鏈櫥褰�"));
 	}
 }
@@ -1003,15 +1331,14 @@
 LRESULT CServoDlg::OnToolbarBtnClicked(WPARAM wParam, LPARAM lParam)
 {
 	int id = (int)lParam;
-	if (id == IDC_BUTTON_RUN || id == IDC_BUTTON_STOP) {
-		UserRole emRole = UserManager::getInstance().getCurrentUserRole();
-		if (emRole != UserRole::SuperAdmin) {
-			AfxMessageBox(_T("褰撳墠鐢ㄦ埛骞堕潪绠$悊鍛橈紒锛侊紒")); 
-			return 1;
-		}
-	}
-
 	if (id == IDC_BUTTON_RUN) {
+		int rc = UX_CanExecute(L"start");
+		if (rc != 1) {
+			AfxMessageBox("鎿嶄綔鏉冮檺涓嶈冻锛岃鑱旂郴绠$悊浜哄憳锛�");
+			return 0;
+		}
+		UX_RecordAction(L"start");
+
 		if (theApp.m_model.getMaster().getState() == SERVO::MASTERSTATE::MSERROR) {
 			AfxMessageBox("褰撳墠鏈夋満鍙板彂鐢熼敊璇紝涓嶈兘鍚姩锛岃纭瑙e喅闂鍚庡啀灏濊瘯閲嶆柊鍚姩锛�");
 		}
@@ -1048,6 +1375,13 @@
 		}
 	}
 	else if (id == IDC_BUTTON_STOP) {
+		int rc = UX_CanExecute(L"stop");
+		if (rc != 1) {
+			AfxMessageBox("鎿嶄綔鏉冮檺涓嶈冻锛岃鑱旂郴绠$悊浜哄憳锛�");
+			return 0;
+		}
+		UX_RecordAction(L"stop");
+
 		if (theApp.m_model.getMaster().stop() == 0) {
 			m_pTopToolbar->GetBtn(IDC_BUTTON_STOP)->EnableWindow(FALSE);
 		}
@@ -1071,6 +1405,16 @@
 		dlg.SetEFEM(pEFEM);
 		dlg.DoModal();
 	}
+	else if (id == IDC_BUTTON_ALARM) {
+		if (m_pAlarmPopupDlg == nullptr) {
+			m_pAlarmPopupDlg = new CAlarmPopupDlg();
+			m_pAlarmPopupDlg->Create(IDD_DIALOG_POPUP_ALARM, this);
+			m_pAlarmPopupDlg->CenterWindow();
+		}
+		m_pAlarmPopupDlg->RefreshContent();
+		m_pAlarmPopupDlg->ShowWindow(SW_SHOW);
+		MarkAlarmsRead();
+	}
 	else if (id == IDC_BUTTON_SETTINGS) {
 		SERVO::CEquipment* pEq = theApp.m_model.m_master.getEquipment(EQ_ID_EFEM);
 		((SERVO::CEFEM*)pEq)->printDebugRobotState();
@@ -1083,6 +1427,27 @@
 	}
 	else if (id == IDC_BUTTON_OPERATOR) {
 		int menuId = (int)wParam;
+		if (menuId == 0) {
+			CUserManager2Dlg dlg;
+			dlg.DoModal();
+		}
+		else if (menuId == 1) {
+			CUserXLogDlg dlg;
+			dlg.DoModal();
+		}
+		else if (menuId == 2) {
+			CLoginDlg2 dlg;
+			if (dlg.DoModal() == IDOK) {
+				bool bRet = CUserManager2::getInstance().login((LPTSTR)(LPCTSTR)dlg.m_strUsername,
+					(LPTSTR)(LPCTSTR)dlg.m_strPassword);
+				if (!bRet) {
+					AfxMessageBox("鐧诲綍澶辫触锛岃妫�鏌ョ敤鎴峰悕鎴栧瘑鐮佹槸鍚︽纭紒");
+				}
+				UpdateLoginStatus();
+			}
+		}
+
+		/*
 		SystemLogManager& logManager = SystemLogManager::getInstance();
 		UserManager& userManager = UserManager::getInstance();
 		if (menuId == 0) {
@@ -1131,6 +1496,7 @@
 		}
 
 		UpdateLoginStatus();
+		*/
 	}
 
 	return 0;
@@ -1160,6 +1526,11 @@
 	return 0;
 }
 
+BOOL CServoDlg::PreTranslateMessage(MSG* pMsg)
+{
+	return CDialogEx::PreTranslateMessage(pMsg);
+}
+
 CString& CServoDlg::GetRuntimeFormatText(CString& strText, const char* pszSuffix)
 {
 	ULONGLONG ullRunTime = (ULONGLONG)(theApp.m_model.getMaster().getRunTime() * 0.001);
@@ -1183,4 +1554,4 @@
 	}
 
 	return strText;
-}
\ No newline at end of file
+}
diff --git a/SourceCode/Bond/Servo/ServoDlg.h b/SourceCode/Bond/Servo/ServoDlg.h
index bdc47a4..e05c9e5 100644
--- a/SourceCode/Bond/Servo/ServoDlg.h
+++ b/SourceCode/Bond/Servo/ServoDlg.h
@@ -3,6 +3,8 @@
 //
 
 #pragma once
+#include <vector>
+#include <unordered_set>
 #include "BlButton.h"
 #include "PageLog.h"
 #include "PageAlarm.h"
@@ -12,12 +14,18 @@
 #include "CPanelMaster.h"
 #include "CPanelEquipment.h"
 #include "CPanelAttributes.h"
+#include "CPanelProduction.h"
 #include "CPageGraph1.h"
 #include "CPageGraph2.h"
+#include "HmTab.h"
 #include "TopToolbar.h"
 #include "CMyStatusbar.h"
 #include "CRobotTaskDlg.h"
 #include "CPageGlassList.h"
+#include "CPageVarialbles.h"
+#include "CPageDataVarialbles.h"
+#include "AlarmPopupDlg.h"
+#include "AlarmManager.h"
 
 
 // CServoDlg 瀵硅瘽妗�
@@ -30,10 +38,16 @@
 
 public:
 	void ShowTerminalText(const char* pszText, unsigned int duration = -1);
+	void AckAlarm(int alarmId);
 
 private:
 	void InitRxWindows();
+	void RefreshAlarmBadge();
+	void MarkAlarmsRead();
+	void RaiseTestAlarm();
+	void ClearTestAlarm();
 	void Resize();
+	void SetLeftPanelType(int type, bool resize = true);
 	void ShowChildPage(int index);
 	void UpdateLoginStatus();
 	CString& GetRuntimeFormatText(CString& strText, const char* pszSuffix);
@@ -49,6 +63,10 @@
 	CPageAlarm*	 m_pPageAlarm;
 	CPageLog*	 m_pPageLog;
 	CPageTransferLog* m_pPageTransferLog;
+	CAlarmPopupDlg* m_pAlarmPopupDlg;
+	CHmTab* m_pTab;
+	std::vector<AlarmData> m_unreadAlarms;
+	std::unordered_set<int> m_ackAlarms;
 
 // 瀵硅瘽妗嗘暟鎹�
 #ifdef AFX_DESIGN_TIME
@@ -64,8 +82,10 @@
 	HICON m_hIcon;
 	COLORREF m_crBkgnd;
 	HBRUSH m_hbrBkgnd;
+	int m_nLeftPanelType; // 1: CPanelMaster, 2: CPanelProduction
 	CTopToolbar* m_pTopToolbar;
 	CPanelMaster* m_pPanelMaster;
+	CPanelProduction* m_pPanelProduction;
 	CPanelEquipment* m_pPanelEquipment;
 	CPanelAttributes* m_pPanelAttributes;
 	CMyStatusbar* m_pMyStatusbar;
@@ -94,16 +114,29 @@
 	afx_msg void OnUpdateMenuFileExit(CCmdUI* pCmdUI);
 	afx_msg void OnMenuProjectVarialbleList();
 	afx_msg void OnUpdateMenuProjectVarialbleList(CCmdUI* pCmdUI);
+	afx_msg void OnMenuTestAlarmOn();
+	afx_msg void OnUpdateMenuTestAlarmOn(CCmdUI* pCmdUI);
+	afx_msg void OnMenuTestAlarmOff();
+	afx_msg void OnUpdateMenuTestAlarmOff(CCmdUI* pCmdUI);
 	afx_msg void OnMenuTestMessageSet();
 	afx_msg void OnUpdateMenuTestMessageSet(CCmdUI* pCmdUI);
 	afx_msg void OnMenuTestMessageClear();
 	afx_msg void OnUpdateMenuTestMessageClear(CCmdUI* pCmdUI);
 	afx_msg void OnMenuToolsClientList();
 	afx_msg void OnUpdateMenuToolsClientList(CCmdUI* pCmdUI);
+	afx_msg void OnMenuToolsCurveEmptyMode();
+	afx_msg void OnUpdateMenuToolsCurveEmptyMode(CCmdUI* pCmdUI);
+	afx_msg void OnMenuToolsCurveProductionMode();
+	afx_msg void OnUpdateMenuToolsCurveProductionMode(CCmdUI* pCmdUI);
+	afx_msg void OnMenuWndTestPanel();
+	afx_msg void OnUpdateMenuWndTestPanel(CCmdUI* pCmdUI);
+	afx_msg void OnMenuWndProPanel();
+	afx_msg void OnUpdateMenuWndProPanel(CCmdUI* pCmdUI);
 	afx_msg void OnMenuHelpAbout();
 	afx_msg void OnTimer(UINT_PTR nIDEvent);
 	afx_msg LRESULT OnPanelResize(WPARAM wParam, LPARAM lParam);
 	afx_msg void OnTabSelChanged(NMHDR* nmhdr, LRESULT* result);
 	LRESULT OnToolbarBtnClicked(WPARAM wParam, LPARAM lParam);
 	LRESULT OnStatusbarBtnClicked(WPARAM wParam, LPARAM lParam);
+	virtual BOOL PreTranslateMessage(MSG* pMsg);
 };
diff --git a/SourceCode/Bond/Servo/ToolUnits.cpp b/SourceCode/Bond/Servo/ToolUnits.cpp
index 6d5df0d..e3e1dca 100644
--- a/SourceCode/Bond/Servo/ToolUnits.cpp
+++ b/SourceCode/Bond/Servo/ToolUnits.cpp
@@ -7,6 +7,8 @@
 #include <ctime>
 #include <iomanip>
 #include <sstream>
+#include <vector>
+#include <cstdarg>
 
 CToolUnits::CToolUnits()
 {
@@ -574,4 +576,65 @@
 	std::ostringstream oss;
 	oss << std::put_time(&tm, "%Y%m%d%H%M%S"); // 例:2025-09-15 08:23:07
 	return oss.str();
-}
\ No newline at end of file
+}
+
+std::wstring CToolUnits::AnsiToWString(const std::string& str)
+{
+	if (str.empty()) return std::wstring();
+
+	int len = ::MultiByteToWideChar(CP_ACP, 0,
+		str.c_str(), -1,
+		nullptr, 0);
+	if (len <= 0) return std::wstring();
+
+	std::wstring ws;
+	ws.resize(len - 1);
+
+	::MultiByteToWideChar(CP_ACP, 0,
+		str.c_str(), -1,
+		&ws[0], len);
+
+	return ws;
+}
+
+std::string CToolUnits::WStringToAnsi(const std::wstring& wstr)
+{
+	if (wstr.empty()) return std::string();
+
+	int len = ::WideCharToMultiByte(CP_ACP, 0,
+		wstr.c_str(), -1,
+		nullptr, 0,
+		nullptr, nullptr);
+	if (len <= 0) return std::string();
+
+	std::string str;
+	str.resize(len - 1);
+
+	::WideCharToMultiByte(CP_ACP, 0,
+		wstr.c_str(), -1,
+		&str[0], len,
+		nullptr, nullptr);
+
+	return str;
+}
+std::string CToolUnits::formatString(const char* fmt, ...)
+{
+    if (fmt == nullptr) return std::string();
+
+    char buf[512] = {0};
+    va_list args;
+    va_start(args, fmt);
+    int n = _vsnprintf_s(buf, sizeof(buf), _TRUNCATE, fmt, args);
+    va_end(args);
+
+    if (n >= 0 && n < (int)sizeof(buf)) {
+        return std::string(buf);
+    }
+
+    // ?????????????????
+    std::vector<char> tmp((n > 0 ? n + 2 : 1024), 0);
+    va_start(args, fmt);
+    _vsnprintf_s(tmp.data(), tmp.size(), _TRUNCATE, fmt, args);
+    va_end(args);
+    return std::string(tmp.data());
+}
diff --git a/SourceCode/Bond/Servo/ToolUnits.h b/SourceCode/Bond/Servo/ToolUnits.h
index f88788d..0d171c7 100644
--- a/SourceCode/Bond/Servo/ToolUnits.h
+++ b/SourceCode/Bond/Servo/ToolUnits.h
@@ -3,6 +3,8 @@
 #include <chrono>
 #include <optional>
 #include <utility>
+#include <vector>
+#include <cstdarg>
 
 enum class QuickRange { Today, Last7Days, ThisMonth, ThisYear };
 using TP = std::chrono::system_clock::time_point;
@@ -60,5 +62,7 @@
 		const char* fmt = "%Y-%m-%d %H:%M:%S");
 	static std::string TimePointToLocalStringMs(const std::optional<TP>& tp);
 	static std::string NowStrSec();
+	static std::wstring AnsiToWString(const std::string& str);
+	static std::string WStringToAnsi(const std::wstring& wstr);
+	static std::string formatString(const char* fmt, ...);
 };
-
diff --git a/SourceCode/Bond/Servo/TopToolbar.cpp b/SourceCode/Bond/Servo/TopToolbar.cpp
index 47d5ee4..f2f37dd 100644
--- a/SourceCode/Bond/Servo/TopToolbar.cpp
+++ b/SourceCode/Bond/Servo/TopToolbar.cpp
@@ -8,9 +8,6 @@
 #include "Common.h"
 
 
-#define SHOW_BATCH	1
-#define SHOW_CT		1
-
 // CTopToolbar 对话框
 
 IMPLEMENT_DYNAMIC(CTopToolbar, CDialogEx)
@@ -73,6 +70,11 @@
 	m_btnOperator.SetMenu(hMenu);
 
 
+	CString strIniFile;
+	strIniFile.Format(_T("%s\\configuration.ini"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir);
+	bool bEnableRunCT = GetPrivateProfileInt("APP", _T("EnableRunCT"), 0, strIniFile) != 0;
+	m_btnRunCt.ShowWindow(bEnableRunCT ? SW_SHOW : SW_HIDE);
+
 	return TRUE;  // return TRUE unless you set the focus to a control
 				  // 异常: OCX 属性页应返回 FALSE
 }
@@ -127,19 +129,19 @@
 	x += BTN_WIDTH;
 	x += 2;
 
-#ifdef SHOW_BATCH
 	pItem = GetDlgItem(IDC_BUTTON_RUN_BATCH);
-	pItem->MoveWindow(x, y, BTN_WIDTH, nBthHeight);
-	x += BTN_WIDTH;
-	x += 2;
-#endif
+	if (::IsWindowVisible(pItem->GetSafeHwnd())) {
+		pItem->MoveWindow(x, y, BTN_WIDTH, nBthHeight);
+		x += BTN_WIDTH;
+		x += 2;
+	}
 
-#ifdef SHOW_CT
 	pItem = GetDlgItem(IDC_BUTTON_RUN_CT);
-	pItem->MoveWindow(x, y, BTN_WIDTH, nBthHeight);
-	x += BTN_WIDTH;
-	x += 2;
-#endif
+	if (::IsWindowVisible(pItem->GetSafeHwnd())) {
+		pItem->MoveWindow(x, y, BTN_WIDTH, nBthHeight);
+		x += BTN_WIDTH;
+		x += 2;
+	}
 
 	pItem = GetDlgItem(IDC_BUTTON_STOP);
 	pItem->MoveWindow(x, y, BTN_WIDTH, nBthHeight);
diff --git a/SourceCode/Bond/Servo/resource.h b/SourceCode/Bond/Servo/resource.h
index 6c9865f..ef26aa5 100644
--- a/SourceCode/Bond/Servo/resource.h
+++ b/SourceCode/Bond/Servo/resource.h
Binary files differ
diff --git a/SourceCode/Bond/Servo/stdafx.h b/SourceCode/Bond/Servo/stdafx.h
index 8e4739a..36ba555 100644
--- a/SourceCode/Bond/Servo/stdafx.h
+++ b/SourceCode/Bond/Servo/stdafx.h
@@ -75,7 +75,7 @@
 
 #include "..\RxWindows1.0\include\RxWindowsLib.h"
 #include "..\HSMSSDK\Include\HSMSSDK.h"
-
+#include "..\UserXLibrary\UserXAPI.h"
 
 
 #ifdef _UNICODE
diff --git a/SourceCode/Bond/USERXLibrary/UserXAPI.h b/SourceCode/Bond/USERXLibrary/UserXAPI.h
new file mode 100644
index 0000000..94e141a
--- /dev/null
+++ b/SourceCode/Bond/USERXLibrary/UserXAPI.h
@@ -0,0 +1,136 @@
+// UserX 用户管理库(C 接口)/ UserX user management library (C API)
+// 覆盖功能 / Features: users, roles, actions, auth, logs(固定 SQLite3 持久化 / SQLite3 persistence)
+
+#pragma once
+
+#if defined(_WIN32)
+#  ifdef USERXLIBRARY_EXPORTS
+#    define UX_API extern "C" __declspec(dllexport)
+#  else
+#    define UX_API extern "C" __declspec(dllimport)
+#  endif
+#else
+#  define UX_API extern "C"
+#endif
+
+#include <wchar.h>
+
+
+#ifdef _COMPILE_AS_LIB
+#warning "compiling as lib!"
+#else
+#ifdef _DEBUG
+#ifndef _WIN64
+#pragma comment(lib, "../USERXLibrary/lib/Win32/Debug/UserXLibrary.lib")
+#else 
+#pragma comment(lib, "../USERXLibrary/lib/x64/Debug/UserXLibrary.lib")
+#endif
+#else 
+#ifndef _WIN64
+#pragma comment(lib, "../USERXLibrary/lib/Win32/Release/UserXLibrary.lib")
+#else 
+#pragma comment(lib, "../USERXLibrary/lib/x64/Release/UserXLibrary.lib")
+#endif
+#endif
+#endif // !BUILD_AS_LIB
+
+
+// 额外错误码 / Extra error code
+#ifndef UX_ERR_BAD_PASSWORD
+#define UX_ERR_BAD_PASSWORD        -10    // 密码错误 / bad password
+#endif
+
+// 错误码宏定义 / Error code macros
+#define UX_OK                        0    // 成功 / success
+#define UX_ERR_INVALID_ARGS         -1    // 参数错误 / invalid arguments
+#define UX_ERR_NOT_FOUND            -2    // 未找到 / not found
+#define UX_ERR_EXISTS               -3    // 已存在 / already exists
+#define UX_ERR_PERMISSION           -4    // 权限不足 / permission denied
+#define UX_ERR_NOT_LOGGED_IN        -5    // 未登录 / not logged in
+#define UX_ERR_BUFFER_TOO_SMALL     -6    // 缓冲区不足 / buffer too small
+#define UX_ERR_DB                   -7    // I/O 或数据库错误 / I/O or database error
+#define UX_ERR_NOT_DEFINED          -8    // 未定义(如动作不存在)/ not defined
+#define UX_ERR_DB_NOT_EMPTY         -9    // 数据库非空(已初始化)/ database not empty
+
+// 返回码 / Return codes(详见上方宏)
+// UX_OK, UX_ERR_INVALID_ARGS, UX_ERR_NOT_FOUND, UX_ERR_EXISTS,
+// UX_ERR_PERMISSION, UX_ERR_NOT_LOGGED_IN, UX_ERR_BUFFER_TOO_SMALL,
+// UX_ERR_DB, UX_ERR_NOT_DEFINED, UX_ERR_DB_NOT_EMPTY
+
+// 初始化:指定数据目录,内部将打开/创建 SQLite 数据库文件 UserX.db
+// Init: provide data directory; opens/creates SQLite DB file UserX.db
+UX_API int UX_Init(const wchar_t* storage_dir);
+
+// 可选:覆盖数据库路径(仅使用第一个参数作为 .db 文件路径,其他忽略)
+// Optional: override DB path (use first arg as .db path; others ignored)
+UX_API int UX_SetStorage(const wchar_t* users_db_path,
+                         const wchar_t* /*unused_logs*/,
+                         const wchar_t* /*unused_roles*/,
+                         const wchar_t* /*unused_actions*/);
+
+// 配置角色(name:level 按行)/ Configure roles from lines "name:level"
+// 仅允许在“空数据库”(roles/users/actions/logs 四表均无数据)时调用;否则返回 -9。
+// Only allowed when DB is empty (roles/users/actions/logs all zero rows); otherwise returns -9.
+// e.g. L"Admin:100\nEngineer:50\nOperator:10\n"
+UX_API int UX_SetRoleDefinitions(const wchar_t* roles_text);
+
+// 获取角色列表(name:level 按行);buffer 为空或不足时返回所需大小
+// Get roles list as lines; returns required size if buffer is null/too small
+UX_API int UX_GetRoles(wchar_t* buffer, int buffer_chars);
+
+// 用户管理 / User management
+UX_API int UX_AddUser(const wchar_t* username,
+                      const wchar_t* display_name,
+                      const wchar_t* password,
+                      const wchar_t* role_name);
+
+UX_API int UX_DeleteUser(const wchar_t* username);
+
+// 更新任意字段(传 nullptr 表示保持不变)/ Update subset; nullptr keeps field
+UX_API int UX_UpdateUser(const wchar_t* username,
+                         const wchar_t* new_display_name,
+                         const wchar_t* new_password,
+                         const wchar_t* new_role_name,
+                         int enabled /* -1 keep, 0 disable, 1 enable */);
+
+UX_API int UX_EnableUser(const wchar_t* username, int enabled /*0/1*/);
+UX_API int UX_ResetPassword(const wchar_t* username, const wchar_t* new_password);
+UX_API int UX_RenameUser(const wchar_t* username, const wchar_t* new_display_name);
+
+// 管理员权限:删除所有用户 / Admin-only
+UX_API int UX_DeleteAllUsers();
+
+// 获取用户列表(每行:username,display,level,enabled)/ Query users
+UX_API int UX_GetUsers(wchar_t* buffer, int buffer_chars);
+
+// 认证 / Authentication
+UX_API int UX_Login(const wchar_t* username, const wchar_t* password);
+UX_API void UX_Logout();
+UX_API int UX_IsLoggedIn();
+UX_API void UX_Shutdown(); // 释放资源 / shutdown and free resources
+UX_API int UX_GetCurrentUser(wchar_t* buffer, int buffer_chars);
+
+// 动作与权限 / Actions & permissions
+// 定义/覆盖动作(名称、描述、最低角色)/ Define/overwrite action
+UX_API int UX_DefineAction(const wchar_t* action_name,
+                           const wchar_t* description,
+                           const wchar_t* min_role_name);
+
+// 可执行返回1,不可执行返回0;负数为错误 / 1 allowed, 0 denied
+UX_API int UX_CanExecute(const wchar_t* action_name);
+
+// 记录动作到日志(时间、用户、动作、描述)/ Record action to logs
+UX_API int UX_RecordAction(const wchar_t* action_name);
+
+// 获取动作列表(每行:name,desc,minlevel)/ Get actions list
+UX_API int UX_GetActions(wchar_t* buffer, int buffer_chars);
+
+// 记录尝试(允许/拒绝)/ record an attempt with allowed flag (1 allowed, 0 denied)
+UX_API int UX_RecordAttempt(const wchar_t* action_name, int allowed /*1/0*/);
+
+// 查询最近 N 条日志(每行:ISO8601时间,用户名,动作,描述)/ Query logs
+UX_API int UX_QueryLogs(int last_n, wchar_t* buffer, int buffer_chars);
+
+// 根据错误码返回文字描述(中文/English)
+// Return a human-readable message for an error code
+UX_API const wchar_t* UX_ErrorMessage(int code);
diff --git a/SourceCode/Bond/x64/Debug/AlarmList.txt b/SourceCode/Bond/x64/Debug/AlarmList.txt
index 69473c8..59dab8f 100644
--- a/SourceCode/Bond/x64/Debug/AlarmList.txt
+++ b/SourceCode/Bond/x64/Debug/AlarmList.txt
@@ -225,3 +225,4 @@
 
 
 
+9000,0,PauseEvent触发
diff --git a/SourceCode/Bond/x64/Debug/CollectionEventList.txt b/SourceCode/Bond/x64/Debug/CollectionEventList.txt
index f955838..b42a9e6 100644
--- a/SourceCode/Bond/x64/Debug/CollectionEventList.txt
+++ b/SourceCode/Bond/x64/Debug/CollectionEventList.txt
@@ -1,18 +1,25 @@
+CEID,CE Name,Descriptions,Attached RPTID
 300,AccessMode_To_Manual,,(300)
 301,AccessMode_To_Auto,,(301)
 600,ControlStateChanged,,(600)
 700,ProcessStateChanged,,(700)
+10018,ProcessDataReport,,(33)
+10015,SubEqpStart,,(10015)
+10016,SubEqpEnd,,(10016)
+10017,SubEqpStateChange,,(10017)
 10000,RecipeChanged,,(10000)
 10030,CarrierArrived,,(10300)
 10031,CarrierRemoved,,(10300)
+10032,LoadPortNotAssoc,,(50014)
 10040,ReadyToLoad,,(10300)
 10041,ReadyToUnLoad,,(10300)
 10051,CarrierIDWaitingForHost,,(10051)
 10052,CarrierIDVerificationOK,,(10052)
 10053,CarrierIDVerificationNG,,(10052)
-10061,SlotMapWaitingForHost,,(10061)
+10061,CheckSlotMap,,(10061)
 10062,SlotMapVerificationOK,,(10062)
 10063,SlotMapVerificationNG,,(10062)
+10064,SlotMapMismatch,Slot map mismatch between scan and download,(10062)
 10071,GlassIDReadWaitingForHost,,(10071)
 10072,GlassIDReadVerificationOK,,(10072)
 10073,GlassIDReadVerificationNG,,(10072)
@@ -48,4 +55,22 @@
 50008,Port_Unload_Ready,,(50008)
 50009,Port_Load_Ready,,(50009)
 50010,Port_Blocked,,(50010)
-
+50011,OCR_PanelID_Read_OK,OCR read OK and match,(50012)
+50015,OCR_PanelID_Read_NG,OCR read fail (key-in match),(50012)
+50016,OCR_PanelID_Read_Mismatch,OCR read OK but mismatch,(50012)
+50017,OCR_PanelID_Read_NG_Mismatch,OCR read fail and key-in mismatch,(50012)
+50012,Port_Ready_To_Release,,(50013)
+50020,PortStateChange,,(50020)
+50021,GlassReceivedJob,Glass received into equipment slot,(50021)
+50022,GlassSentOutJob,Glass sent out from equipment slot,(50022)
+60000,BonderSVData,,(60000)
+61000,BonderProcessData,,(61000)
+62000,VacuumBakeSVData,,(62000)
+63000,VacuumBakeProcessData,,(63000)
+64000,BakeCoolingSVData,,(64000)
+65000,BakeCoolingProcessData,,(65000)
+66000,MeasurementSVData,,(66000)
+67000,MeasurementProcessData,,(67000)
+12000,UnitStart,,(12000)
+12001,UnitStateChange,,(12001)
+12002,UnitEnd,,(12002)
diff --git a/SourceCode/Bond/x64/Debug/DataVariableList.txt b/SourceCode/Bond/x64/Debug/DataVariableList.txt
new file mode 100644
index 0000000..db4f8a2
--- /dev/null
+++ b/SourceCode/Bond/x64/Debug/DataVariableList.txt
@@ -0,0 +1,98 @@
+DVID,DV Name,DV Format,DV Remark
+6000,Bonder_SV_ProcessStep,A20,Bonder SV: 宸ヨ壓杩愯姝ラ
+6001,Bonder_SV_BladderPressure,A20,Bonder SV: 姘斿泭鍘嬪姏褰撳墠
+6002,Bonder_SV_UpperChamberPressure,A20,Bonder SV: 涓婅厰鍘嬪姏鍚堣
+6003,Bonder_SV_PipeVacuumGauge,A20,Bonder SV: 绠¢亾鐪熺┖琛�
+6004,Bonder_SV_ChamberVacuumGauge,A20,Bonder SV: 鑵斾綋鐪熺┖琛�
+6005,Bonder_SV_UpperTemp1,A20,Bonder SV: 涓婅厰娓╁害1
+6006,Bonder_SV_UpperTemp2,A20,Bonder SV: 涓婅厰娓╁害2
+6007,Bonder_SV_UpperTemp3,A20,Bonder SV: 涓婅厰娓╁害3
+6008,Bonder_SV_UpperTemp4,A20,Bonder SV: 涓婅厰娓╁害4
+6009,Bonder_SV_UpperTemp5,A20,Bonder SV: 涓婅厰娓╁害5
+6010,Bonder_SV_UpperTemp6,A20,Bonder SV: 涓婅厰娓╁害6
+6011,Bonder_SV_LowerTemp1,A20,Bonder SV: 涓嬭厰娓╁害1
+6012,Bonder_SV_LowerTemp2,A20,Bonder SV: 涓嬭厰娓╁害2
+6013,Bonder_SV_LowerTemp3,A20,Bonder SV: 涓嬭厰娓╁害3
+6014,Bonder_SV_LowerTemp4,A20,Bonder SV: 涓嬭厰娓╁害4
+6015,Bonder_SV_LowerTemp5,A20,Bonder SV: 涓嬭厰娓╁害5
+6016,Bonder_SV_LowerTemp6,A20,Bonder SV: 涓嬭厰娓╁害6
+6017,Bonder_SV_HeatingRemaining,A20,Bonder SV: 鍔犵儹鍓╀綑鏃堕棿
+6018,Bonder_SV_PressingRemaining,A20,Bonder SV: 鍘嬪悎鍓╀綑鏃堕棿
+6100,Bonder_PD_AlignDelay,A20,Bonder PD: 瀵逛綅寤舵椂
+6101,Bonder_PD_DwellTime,A20,Bonder PD: 淇濆帇鏃堕棿
+6102,Bonder_PD_BreakVacuumDelay,A20,Bonder PD: 鐮寸湡绌哄欢鏃�
+6103,Bonder_PD_TurboPumpStartDelay,A20,Bonder PD: 鍒嗗瓙娉靛惎鍔ㄥ欢鏃�
+6104,Bonder_PD_AttachVacuumDelay,A20,Bonder PD: 鎶界湡绌哄欢鏃�
+6105,Bonder_PD_HeatingWaitDelay,A20,Bonder PD: 绛夊緟鍔犵儹寤舵椂
+6106,Bonder_PD_BladderPressureSet,A20,Bonder PD: 姘斿泭鍘嬪姏璁惧畾
+6107,Bonder_PD_BladderPressurizeRate,A20,Bonder PD: 姘斿泭鍔犲帇閫熺巼
+6108,Bonder_PD_BladderDepressurizeRate,A20,Bonder PD: 姘斿泭娉勫帇閫熺巼
+6109,Bonder_PD_AttachPressureLimit,A20,Bonder PD: 璐村悎鍘嬪姏涓婇檺
+6110,Bonder_PD_UpperZTorqueSpeed,A20,Bonder PD: 涓婅厰Z棰勮创鍚堥�熷害
+6111,Bonder_PD_UpperTempSet,A20,Bonder PD: 涓婅厰娓╁害璁惧畾
+6112,Bonder_PD_LowerTempSet,A20,Bonder PD: 涓嬭厰娓╁害璁惧畾
+6113,Bonder_PD_PreAttachSpeed,A20,Bonder PD: 棰勮创鍚堥�熷害
+6114,Bonder_PD_AttachSpeed,A20,Bonder PD: 璐村悎閫熷害
+6115,Bonder_PD_UpperHeatDistance,A20,Bonder PD: 涓婅厰鍔犵儹璺濈
+6116,Bonder_PD_AttachPressIn,A20,Bonder PD: 璐村悎鍘嬪叆鍔�
+6117,Bonder_PD_UpperBreakVacuumDist,A20,Bonder PD: 涓婅厰鐮寸湡绌鸿窛绂�
+6118,Bonder_PD_LowerPinBreakVacuumDist,A20,Bonder PD: 涓嬮《閽堢牬鐪熺┖璺濈
+6119,Bonder_PD_LowerPinHeatDistance,A20,Bonder PD: 涓嬮《閽堝姞鐑窛绂�
+6120,Bonder_PD_PumpGaugeSet,A20,Bonder PD: 鐪熺┖娉电湡绌鸿瀹�
+6121,Bonder_PD_TurboReachSet,A20,Bonder PD: 鍒嗗瓙娉靛埌杈捐瀹�
+6200,VacuumBake_SV_A_ProcessStep,A20,VacuumBake SV A: 宸ヨ壓杩愯姝ラ
+6201,VacuumBake_SV_A_ChamberVacuum,A20,VacuumBake SV A: 鑵斾綋鐪熺┖
+6202,VacuumBake_SV_A_Temp1,A20,VacuumBake SV A: 娓╁害1
+6203,VacuumBake_SV_A_Temp2,A20,VacuumBake SV A: 娓╁害2
+6204,VacuumBake_SV_A_Temp4,A20,VacuumBake SV A: 娓╁害4
+6205,VacuumBake_SV_A_Temp5,A20,VacuumBake SV A: 娓╁害5
+6206,VacuumBake_SV_A_Temp6,A20,VacuumBake SV A: 娓╁害6
+6207,VacuumBake_SV_A_Temp7,A20,VacuumBake SV A: 娓╁害7
+6208,VacuumBake_SV_A_BakeRemaining,A20,VacuumBake SV A: 鐑樼儰鍓╀綑鏃堕棿
+6209,VacuumBake_SV_B_ProcessStep,A20,VacuumBake SV B: 宸ヨ壓杩愯姝ラ
+6210,VacuumBake_SV_B_ChamberVacuum,A20,VacuumBake SV B: 鑵斾綋鐪熺┖
+6211,VacuumBake_SV_B_Temp1,A20,VacuumBake SV B: 娓╁害1
+6212,VacuumBake_SV_B_Temp2,A20,VacuumBake SV B: 娓╁害2
+6213,VacuumBake_SV_B_Temp4,A20,VacuumBake SV B: 娓╁害4
+6214,VacuumBake_SV_B_Temp5,A20,VacuumBake SV B: 娓╁害5
+6215,VacuumBake_SV_B_Temp6,A20,VacuumBake SV B: 娓╁害6
+6216,VacuumBake_SV_B_Temp7,A20,VacuumBake SV B: 娓╁害7
+6217,VacuumBake_SV_B_BakeRemaining,A20,VacuumBake SV B: 鐑樼儰鍓╀綑鏃堕棿
+6300,VacuumBake_PD_ParamIndex,A20,VacuumBake PD: 鍙傛暟搴忓彿
+6301,VacuumBake_PD_HeatTime,A20,VacuumBake PD: 鍔犵儹鏃堕棿
+6302,VacuumBake_PD_BreakVacuumTime,A20,VacuumBake PD: 鐮寸湡绌烘椂闂�
+6303,VacuumBake_PD_VacuumReach,A20,VacuumBake PD: 鐪熺┖杈惧埌鍊�
+6304,VacuumBake_PD_TempSet,A20,VacuumBake PD: 涓绘帶娓╁害璁惧畾
+6400,BakeCooling_SV_A_BakeStep,A20,BakeCooling SV A: 鐑樼儰姝ラ
+6401,BakeCooling_SV_A_Temp1,A20,BakeCooling SV A: 娓╁害1
+6402,BakeCooling_SV_A_Temp2,A20,BakeCooling SV A: 娓╁害2
+6403,BakeCooling_SV_A_Temp4,A20,BakeCooling SV A: 娓╁害4
+6404,BakeCooling_SV_A_Temp5,A20,BakeCooling SV A: 娓╁害5
+6405,BakeCooling_SV_A_Temp6,A20,BakeCooling SV A: 娓╁害6
+6406,BakeCooling_SV_A_Temp7,A20,BakeCooling SV A: 娓╁害7
+6407,BakeCooling_SV_A_BakeRemaining,A20,BakeCooling SV A: 鐑樼儰鍓╀綑鏃堕棿
+6408,BakeCooling_SV_A_CoolStep,A20,BakeCooling SV A: 鍐峰嵈姝ラ
+6409,BakeCooling_SV_A_CoolRemaining,A20,BakeCooling SV A: 鍐峰嵈鍓╀綑鏃堕棿
+6410,BakeCooling_SV_B_BakeStep,A20,BakeCooling SV B: 鐑樼儰姝ラ
+6411,BakeCooling_SV_B_Temp1,A20,BakeCooling SV B: 娓╁害1
+6412,BakeCooling_SV_B_Temp2,A20,BakeCooling SV B: 娓╁害2
+6413,BakeCooling_SV_B_Temp4,A20,BakeCooling SV B: 娓╁害4
+6414,BakeCooling_SV_B_Temp5,A20,BakeCooling SV B: 娓╁害5
+6415,BakeCooling_SV_B_Temp6,A20,BakeCooling SV B: 娓╁害6
+6416,BakeCooling_SV_B_Temp7,A20,BakeCooling SV B: 娓╁害7
+6417,BakeCooling_SV_B_BakeRemaining,A20,BakeCooling SV B: 鐑樼儰鍓╀綑鏃堕棿
+6418,BakeCooling_SV_B_CoolStep,A20,BakeCooling SV B: 鍐峰嵈姝ラ
+6419,BakeCooling_SV_B_CoolRemaining,A20,BakeCooling SV B: 鍐峰嵈鍓╀綑鏃堕棿
+6500,BakeCooling_PD_ParamIndex,A20,BakeCooling PD: 鍙傛暟搴忓彿
+6501,BakeCooling_PD_TimeOrTemp1,A20,BakeCooling PD: 鏃堕棿/娓╁害1
+6502,BakeCooling_PD_TimeOrTemp2,A20,BakeCooling PD: 鏃堕棿/娓╁害2
+6503,BakeCooling_PD_Reserved,A20,BakeCooling PD: 棰勭暀
+6600,Measurement_SV_ProcessStep,A20,Measurement SV: 宸ヨ壓杩愯姝ラ
+6601,Measurement_SV_AOIScore,A20,Measurement SV: AOI鍒嗘暟
+6700,Measurement_PD_ParamIndex,A20,Measurement PD: 鍙傛暟搴忓彿
+6701,Measurement_PD_Time,A20,Measurement PD: 鏃堕棿
+6702,Measurement_PD_Value1,A20,Measurement PD: 娴嬮噺鍊�1
+6703,Measurement_PD_Value2,A20,Measurement PD: 娴嬮噺鍊�2
+10200,SlotMap,U1,SlotMap(Scan)
+10201,SlotMapScan,U1,SlotMap(Scan)
+10202,SlotMapDownload,U1,SlotMap(Download)
diff --git a/SourceCode/Bond/x64/Debug/ReportList.txt b/SourceCode/Bond/x64/Debug/ReportList.txt
index ceadc9c..b4d31bc 100644
--- a/SourceCode/Bond/x64/Debug/ReportList.txt
+++ b/SourceCode/Bond/x64/Debug/ReportList.txt
@@ -1,17 +1,23 @@
-RPTID,(VID1,VID2,...)
+锘縍PTID,(VID1,VID2,...)
+# Notes:
+# - RPTIDs 60000-67000, 700, 10017, 20000 include SubEqpName(5018) and SubEqpSlot(5019).
+# - RPTIDs 50008/50009/50010/50013/50014/50020 use PortId(5022).
+# - OCR events (CEID 50011/50015/50016/50017) use RPTID 50012 with VCRPanelID(5014).
+
+33,(5017)
 300,(1,300)
 301,(1,300)
 600,(500,600,601)
-700,(500,700,701)
+700,(500,5018,5019,700,701)
 10000,(200,201)
-10300,(1,10000)
-10051,(1,10000,10100,10101)
-10061,(1,10000,10100,10200)
-10062,(1,10000,10100,10201,10202)
-10071,(1,10000,10100,10203,20000)
-10072,(1,10000,10100,10203,20000,20001)
-10080,(1,10000,10100)
-20000,(1,10000,10203)
+10300,(1,10000,10001,10002,10003)
+10051,(1,10000,10001,10002,10003,10100,10101)
+10061,(1,10000,10001,10002,10003,10100,10200)
+10062,(1,10000,10001,10002,10003,10100,10201,10202)
+10071,(1,10000,10001,10002,10003,10100,10203,20000)
+10072,(1,10000,10001,10002,10003,10100,10203,20000,20001)
+10080,(1,10000,10001,10002,10003,10100)
+20000,(1,5018,5019,10000,10001,10002,10003,10203)
 30000,(1,30000,30001)
 31000,(1,31000,31001)
 40000,(1,10203,20000)
@@ -23,8 +29,28 @@
 50005,(5007)
 50006,(5008)
 50007,(5009)
-50008,(5010)
-50009,(5011)
-50010,(5012)
-
+50008,(5022)
+50009,(5022)
+50010,(5022)
+50011,(5013)
+50012,(5014)
+50013,(5022)
+50014,(5022)
+50020,(500,5022,5021)
+50021,(5018,5019,5023)
+50022,(5018,5019,5023)
+60000,(500,5018,5019,6000,6001,6002,6003,6004,6005,6006,6007,6008,6009,6010,6011,6012,6013,6014,6015,6016,6017,6018)
+61000,(500,5018,5019,6100,6101,6102,6103,6104,6105,6106,6107,6108,6109,6110,6111,6112,6113,6114,6115,6116,6117,6118,6119,6120,6121)
+62000,(500,5018,5019,6200,6201,6202,6203,6204,6205,6206,6207,6208,6209,6210,6211,6212,6213,6214,6215,6216,6217)
+63000,(500,5018,5019,6300,6301,6302,6303,6304)
+64000,(500,5018,5019,6400,6401,6402,6403,6404,6405,6406,6407,6408,6409,6410,6411,6412,6413,6414,6415,6416,6417,6418,6419)
+65000,(500,5018,5019,6500,6501,6502,6503)
+66000,(500,5018,5019,6600,6601)
+67000,(500,5018,5019,6700,6701,6702,6703)
+10015,(5018,5019)
+10016,(5018,5019)
+10017,(500,5018,5019)
+12000,(500,5018,5019)
+12001,(500,5018,5019)
+12002,(500,5018,5019)
 
diff --git a/SourceCode/Bond/x64/Debug/VariableList.txt b/SourceCode/Bond/x64/Debug/VariableList.txt
index 659b40e..06499c8 100644
--- a/SourceCode/Bond/x64/Debug/VariableList.txt
+++ b/SourceCode/Bond/x64/Debug/VariableList.txt
@@ -1,49 +1,42 @@
 SVID,SV Name,SV Format,SV Remark
-100,PortTransferState,U1,0=OutOfService\r\n1=TransferBlocked\r\n2=ReadyToLoad\r\n3=ReadyToUnload\r\n4=InService\r\n5=TransferReady
-300,AccessMode,U1,1=Manual\r\n2=Auto
-500,Clock,A50,
-600,CurrentControlState,U1,0:Offline:equipment\r\n1:Offline-Attempt\r\n2:Online\r\n3:Offline:host\r\n4:Online:Local\r\n5:Online:Remote
-601,PreviousControlState,U1,
-700,CurrentProcessState,U1,0:DOWN\r\n1:IDLE\r\n2.SETUP\r\n3.EXCUTING\r\n4.MAINTAIN\r\n5.ALARM
-701,PreviousProcessState,U1,
-800,EFEMPPExecName,A20,
-801,EQPPExecName,A20,
-2000,RbRAxisTorque,I2,鏈哄櫒浜篟杞存壄鐭�
-2001,RbLAxisTorque,l2,鏈哄櫒浜篖杞存壄鐭�
-2002,RbZAxisTorque,l2,鏈哄櫒浜篫杞存壄鐭�
-2003,RbTHAxisTorque,l2,鏈哄櫒浜篢H杞存壄鐭�
-2004,RbXAxisTorque,l2,鏈哄櫒浜篨杞存壄鐭�
-2005,AxisX111,l2,X111鐩告満鍓嶇Щ鏍界數鏈烘壄鐭�
-2006,AxisX112,l2,X112鐩告満鍚庣Щ鏍界數鏈烘壄鐭�
-2007,AxisU113,l2,U113浜у搧鏃嬭浆鐢垫満鎵煩
-2008,AxisX114,l2,X114浜у搧宸︽暣鍒楃數鏈烘壄鐭�
-2009,AxisY121,l2,Y121浜у搧鍙虫暣鍒楃數鏈烘壄鐭�
-2010,AxisY122,l2,Y122浜у搧鍓嶆暣鍒楃數鏈烘壄鐭�
-2011,AxisY123,l2,Y123浜у搧鍚庨樀鍒楃數鏈烘壄鐭�
-2012,MainAir,U2,鎬昏繘姘斿帇鍔涘��
-2013,MainVacuum,l2,鎬荤湡绌哄帇鍔涘��
-2014,RbMainVacuum,l2,鏈哄櫒浜虹湡绌哄��
-2015,LPMainVacuum,l2,LP鐪熺┖鍊�#D265
-2016,LPMainAir,U2,LP鍘嬬┖鍊�
-2017,ALVacuum,l2,Aligner鐪熺┖鍊�
-2018,FFU1RPM,U2,FFU1杞��
-2019,FFU2RPM,U2,FFU2杞��
-2020,FFU3RPM,U2,FFU3杞��
-2021,FFU4RPM,U2,FFU4杞��
-2022,ESDValue,I2,闈欑數妫�娴嬪��
-2023,OCREnable,U2,"OCR浣胯兘锛歄:寮�鍚� 1锛氬睆钄�"
-2024,CCDEnable,U2,"CCD浣胯兘锛歄:寮�鍚� 1锛氬睆钄�"
-2025,FFUParameter,U2,FFU璁惧畾鍊�
-5000,CarrierID,A20,鍗″專ID
+10000,CarrierID_P1,A50,Carrier ID for Port 1
+10001,CarrierID_P2,A50,Carrier ID for Port 2
+10002,CarrierID_P3,A50,Carrier ID for Port 3
+10003,CarrierID_P4,A50,Carrier ID for Port 4
+100,PortTransferState_P1,U1,1=LoadReady;2=Loaded;3=InUse;4=UnloadReady;5=Empty;6=Blocked
+101,PortTransferState_P2,U1,1=LoadReady;2=Loaded;3=InUse;4=UnloadReady;5=Empty;6=Blocked
+102,PortTransferState_P3,U1,1=LoadReady;2=Loaded;3=InUse;4=UnloadReady;5=Empty;6=Blocked
+103,PortTransferState_P4,U1,1=LoadReady;2=Loaded;3=InUse;4=UnloadReady;5=Empty;6=Blocked
+300,AccessMode_P1,U1,0=OutOfService;1=TransferBlocked;2=ReadyToLoad;3=ReadyToUnload;4=InService;5=TransferReady
+301,AccessMode_P2,U1,0=OutOfService;1=TransferBlocked;2=ReadyToLoad;3=ReadyToUnload;4=InService;5=TransferReady
+302,AccessMode_P3,U1,0=OutOfService;1=TransferBlocked;2=ReadyToLoad;3=ReadyToUnload;4=InService;5=TransferReady
+303,AccessMode_P4,U1,0=OutOfService;1=TransferBlocked;2=ReadyToLoad;3=ReadyToUnload;4=InService;5=TransferReady
+500,Clock,A50,Current timestamp string
+600,CurrentControlState,U1,0=OfflineEquipment;1=OfflineAttempt;2=Online;3=OfflineHost;4=OnlineLocal;5=OnlineRemote
+601,PreviousControlState,U1,Previous control state (same code set as CurrentControlState)
+700,CurrentProcessState,U1,0=Ready;1=Processing;2=Complete;3=Error
+701,PreviousProcessState,U1,Previous process state (0=Ready;1=Processing;2=Complete;3=Error)
+800,EFEMPPExecName,A20,Current PPExec name from EFEM
+801,EQPPExecName,A20,Current PPExec name from equipment
+8100,Bonder1CurrentRecipe,A50,Current recipe for Bonder1
+8101,Bonder2CurrentRecipe,A50,Current recipe for Bonder2
+8102,VacuumBakeCurrentRecipe,A50,Current recipe for VacuumBake
+8103,BakeCoolingCurrentRecipe,A50,Current recipe for BakeCooling
+8104,MeasurementCurrentRecipe,A50,Current recipe for Measurement
+8105,EFEMCurrentRecipe,A50,Current recipe for EFEM
 5001,CJobSpace,U1,CJ Space
 5002,PJobSpace,U1,PJ Space
-5003,PJQueued,L,PJ Queued
-5004,PJStartID,A20,PJStartID
-5005,PJEndID,A20,PJEndID
-5006,PanelStartID,A20,PanelStartID
-5007,PanelEndID,A20,PanelEndID
-5008,CJStartID,A20,CJStartID
-5009,CJEndID,A20,CJEndID
-5010,UnloadReadyPortId,U2,"Port ID"
-5011,LoadReadyPortId,U2,"Port ID"
-5012,BlockedPortId,U2,"Port ID"
\ No newline at end of file
+5003,PJQueued,L,PJ queued list (IDs)
+5004,PJStartID,A20,PJ start ID
+5005,PJEndID,A20,PJ end ID
+5006,PanelStartID,A20,Panel start ID
+5007,PanelEndID,A20,Panel end ID
+5008,CJStartID,A20,CJ start ID
+5009,CJEndID,A20,CJ end ID
+5014,VCRPanelID,A20,Panel ID from reader
+5017,ProcessDataReportText,A50,EV_PROCESS_DATA_REPORT payload (placeholder)
+5018,SubEqpName,A20,Sub equipment name (SubEqp/Unit/Process/SV/ProcessData events)
+5019,SubEqpSlot,U1,Slot number for SubEqp/Unit; 0 when not applicable
+5021,PortState,U1,Port transfer/state code for PortStateChange
+5022,PortId,U1,Unified port ID for all Port events
+5023,MaterialId,A50,Material/Glass ID for Received/SentOut events
diff --git a/SourceCode/Bond/x64/Debug/test.ini b/SourceCode/Bond/x64/Debug/test.ini
new file mode 100644
index 0000000..bf28634
--- /dev/null
+++ b/SourceCode/Bond/x64/Debug/test.ini
@@ -0,0 +1,10 @@
+[SimEap]
+; Enable/disable simulator (1=on, 0=off)
+Enabled=1
+
+; Step interval in milliseconds
+IntervalMs=2000
+
+; Run a single step once. Change Step value to trigger again.
+; 0 means do nothing.
+Step=1
diff --git a/SourceCode/Bond/x64/Release/AlarmList.txt b/SourceCode/Bond/x64/Release/AlarmList.txt
index 34ad3a9..40bca15 100644
--- a/SourceCode/Bond/x64/Release/AlarmList.txt
+++ b/SourceCode/Bond/x64/Release/AlarmList.txt
@@ -225,3 +225,4 @@
 
 
 
+9000,0,PauseEvent触发

--
Gitblit v1.9.3