mrDarker
6 天以前 829fe6c6bc33d53fda9c31fd45a37e1df87befff
Merge branch 'clh' into liuyang
已添加38个文件
已修改87个文件
13458 ■■■■ 文件已修改
.gitignore 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Document/EventSummary_v2.1.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
Document/Panel Bonder八零联合 SecsTest CheckList_v3.0.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
Document/VariableList.txt 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/BLControlsSDK/include/BLLabel.h 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/BondEq/AccordionWnd.cpp 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/BondEq/BondEq.vcxproj 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/CHsmsActive.cpp 196 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/CHsmsActive.h 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/CPJsDlg.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/EAPSimulator.rc 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.cpp 349 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/EAPSimulatorDlg.h 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/EAPSimulator/Resource.h 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/AccordionWnd.cpp 787 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/AccordionWnd.h 131 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/AlarmManager.cpp 420 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/AlarmManager.h 205 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/AlarmPopupDlg.cpp 321 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/AlarmPopupDlg.h 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CBonder.cpp 236 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CBonder.h 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CCollectionEvent.cpp 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CCollectionEvent.h 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CControlJobManagerDlg.cpp 86 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CDataVariable.h 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEquipment.cpp 184 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEquipment.h 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEventEditDlg.cpp 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEventEditDlg.h 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CGlass.cpp 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CHMPropertyDlg.cpp 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CHMPropertyDlg.h 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CHMPropertyPage.cpp 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CHMPropertyPage.h 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CLoadPort.cpp 108 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CLoadPort.h 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMaster.cpp 1055 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMaster.h 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMyStatusbar.cpp 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMyStatusbar.h 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageCollectionEvent.cpp 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageCollectionEvent.h 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageCtrlState.cpp 157 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageCtrlState.h 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageDataVarialbles.cpp 220 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageDataVarialbles.h 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageGlassList.cpp 120 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageGlassList.h 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageGraph1.cpp 60 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageGraph2.cpp 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageGraph2.h 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageProdOverview.cpp 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageProdOverview.h 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageReport.cpp 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageReport.h 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageVarialbles.cpp 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageVarialbles.h 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPanelMaster.cpp 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPanelMaster.h 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPanelProduction.cpp 236 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPanelProduction.h 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CParam.cpp 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CParam.h 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CReadStep.cpp 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CReadStep.h 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CReport.cpp 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CReport.h 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CReportEditDlg.cpp 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CReportEditDlg.h 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CSVData.cpp 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CServoUtilsTool.cpp 69 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CServoUtilsTool.h 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CUserEdit2Dlg.cpp 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CUserEdit2Dlg.h 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CUserManager2.cpp 250 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CUserManager2.h 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CUserManager2Dlg.cpp 322 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CUserManager2Dlg.h 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CUserXLogDlg.cpp 178 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CUserXLogDlg.h 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CVariable.h 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CVariableEditDlg2.cpp 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CVariableEditDlg2.h 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ClientListDlg.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Common.h 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Configuration.h 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ConfigurationProduction.cpp 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/EqsGraphWnd.cpp 315 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/EqsGraphWnd.h 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/HmLabel.cpp 177 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/HmLabel.h 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/HsmsAction.cpp 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/HsmsPassive.cpp 2216 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/HsmsPassive.h 144 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/LoginDlg2.cpp 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/LoginDlg2.h 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Model.cpp 668 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Model.h 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/PageRecipe.cpp 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/PortConfigurationDlg.cpp 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ProcessJob.cpp 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ProcessJob.h 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ProductionStats.cpp 438 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ProductionStats.h 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.cpp 140 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.rc 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj.filters 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj.user 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ServoDlg.cpp 457 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ServoDlg.h 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ToolUnits.cpp 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ToolUnits.h 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/TopToolbar.cpp 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/resource.h 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/stdafx.h 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/USERXLibrary/UserXAPI.h 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/x64/Debug/AlarmList.txt 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/x64/Debug/CollectionEventList.txt 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/x64/Debug/DataVariableList.txt 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/x64/Debug/ReportList.txt 54 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/x64/Debug/VariableList.txt 85 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/x64/Debug/test.ini 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/x64/Release/AlarmList.txt 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.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
Document/EventSummary_v2.1.xlsx
Binary files differ
Document/Panel Bonder°ËÁãÁªºÏ SecsTest CheckList_v3.0.xlsx
Binary files differ
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,
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
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)
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>
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函数
// é€šç”¨çš„reply ack函数
void CHsmsActive::replyAck(int s, int f, unsigned int systemBytes, BYTE ack, const char* pszAckName)
{
    IMessage* pMessage = NULL;
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函数
    // é€šè¿‡çš„reply函数
    void replyAck(int s, int f, unsigned int systemBytes, BYTE ack, const char* pszAckName);
    // reply ack0
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);
    }
SourceCode/Bond/EAPSimulator/EAPSimulator.rc
Binary files differ
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());
}
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();
};
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
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);
}
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);
};
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和单元ID组合作为主键
    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是主键
    // åˆ›å»ºæŠ¥è­¦è¡¨ï¼ŒæŠ¥è­¦è®°å½•çš„alarm_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;
}
// ç­›é€‰æŠ¥è­¦æ•°æ®
// èŽ·å–å½“å‰æœªç»“æŸçš„æŠ¥è­¦ï¼ˆend_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和单元ID获取单元名称
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 è§£é™¤æŠ¥è­¦ï¼ˆæ›´æ–°ç»“束时间)
bool AlarmManager::clearAlarmByEventId(int alarmEventId, const std::string& endTime) {
    if (!m_pDB) {
        return false;
@@ -745,7 +827,7 @@
    return result;
}
// é€šè¿‡å¤šä¸ªå±žæ€§æŸ¥æ‰¾å¹¶è§£é™¤æŠ¥è­¦ï¼ˆæ›´æ–°ç»“束时间)
// é€šè¿‡å¤šä¸ªå±žæ€§æŸ¥æ‰¾å¹¶è§£é™¤æŠ¥è­¦ï¼ˆæ›´æ–°ç»“束时间)
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;
        }
    }
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,失败返回false
     */
    bool initAlarmTable();
    /**
     * é”€æ¯æŠ¥è­¦è¡¨
     * é”€æ¯æŠ¥è­¦è¡¨
     */
    void termAlarmTable();
    /**
     * é”€æ¯æŠ¥è­¦è¡¨
     * @return æˆåŠŸè¿”å›žtrue,失败返回false
     * é”€æ¯æŠ¥è­¦è¡¨
     * @return æˆåŠŸè¿”å›žtrue,失败返回false
     */
    bool destroyAlarmTable();
    /**
    * æ’入模拟数据
    * æ’入模拟数据
    */
    void insertMockData();
    /**
     * æ·»åŠ æŠ¥è­¦
     * @param alarmData æŠ¥è­¦æ•°æ®çš„结构体
     * @param alarmEventId æœ€è¿‘插入的 alarm_event_id
     * @return æˆåŠŸè¿”å›žtrue,失败返回false
     * æ·»åŠ æŠ¥è­¦
     * @param alarmData æŠ¥è­¦æ•°æ®çš„结构体
     * @param alarmEventId æœ€è¿‘插入的 alarm_event_id
     * @return æˆåŠŸè¿”å›žtrue,失败返回false
     */
    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,失败返回false
     */
    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和单元ID获取单元名称
    * @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解除报警(更新结束时间)
    * @param alarmEventId äº‹ä»¶ID
    * @param endTime ç»“束时间
    * @return æˆåŠŸè¿”å›žtrue,失败返回false
    */
    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
    * é€šè¿‡å¤šä¸ªå±žæ€§æŸ¥æ‰¾å¹¶è§£é™¤æŠ¥è­¦ï¼ˆæ›´æ–°ç»“束时间)
    * @param nId æŠ¥è­¦ID
    * @param nSeverityLevel æŠ¥è­¦ç­‰çº§
    * @param nDeviceId è®¾å¤‡ID
    * @param nUnitId å•å…ƒID
    * @param strDescription æè¿°
    * @param endTime ç»“束时间
    * @return æˆåŠŸè¿”å›žtrue,失败返回false
    */
    bool clearAlarmByAttributes(int nId, int nDeviceId, int nUnitId, const std::string& endTime);
    /**
     * è¯»å–报警文件
     * @param filename æ–‡ä»¶å
     * @return æˆåŠŸè¿”å›žtrue,失败返回false
     * è¯»å–报警文件
     * @param filename æ–‡ä»¶å
     * @return æˆåŠŸè¿”å›žtrue,失败返回false
     */
    bool readAlarmFile(const std::string& filename);
    /**
     * ä¿å­˜æŠ¥è­¦æ–‡ä»¶
     * @param filename æ–‡ä»¶å
     * @return æˆåŠŸè¿”å›žtrue,失败返回false
     * ä¿å­˜æŠ¥è­¦æ–‡ä»¶
     * @param filename æ–‡ä»¶å
     * @return æˆåŠŸè¿”å›žtrue,失败返回false
     */
    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;
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);
    // è§£é™¤è­¦å‘ŠæŒ‰é’®
    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();
    }
}
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);
};
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列表
    // å¿…须要实现的虚函数,在此初始化Pin列表
    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, åˆ†åˆ«ä¸ºmsg id和panel id
            // 65538, 2为msg id, 1为panel 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) {
                        // æ­¤å¤„解释配方数据
                        // æ­¤å¤„解释配方数据
                        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) {
                        // æ­¤å¤„解释配方数据
                        // æ­¤å¤„解释配方数据
                        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信息
    // å¿…须要实现的虚函数,在此初始化Slot信息
    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, ä¸”pProcessData中的id能匹配G1或G2
            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,错误!不满足两片玻璃且分别为G1与G2的条件,请检查数据是否正确!", 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,错误!两片玻璃未匹配,必须分别为G1和G2类型,请检查数据是否正确!", 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();
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);
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();
    }
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;
    };
}
}
SourceCode/Bond/Servo/CControlJobManagerDlg.cpp
@@ -534,15 +534,6 @@
    m_pControlJob->setPJs(pjs);
    m_pControlJob->clearIssues();
    int nRet = master.setProcessJobs(pjs);
    // æ²¡æœ‰é—®é¢˜çš„pj要释放
    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++) {
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;
    };
}
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状态
        // æ¯éš”一秒,检查一下ALIVE状态
        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 @@
        }
        */
        // è¿žæŽ¥ä¿¡å·è§£é‡Šå’Œä¿å­˜
        // è¿žæŽ¥ä¿¡å·è§£é‡Šå’Œä¿å­˜
        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
                // æ­¤å¤„å°†value按高低位拆分为message id和panel 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
        // æ·»åŠ åˆ°Pin列表,看是输入pin或输出pin
        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>的Intent<%d,%s,0x%x>",
            this->getName().c_str(),
            pPin->getName().c_str(),
            pFromEq->getName().c_str(),
@@ -869,11 +892,11 @@
        // ä»¥ä¸‹è§£é‡Šå¤„理数据
        // ä»¥ä¸‹è§£é‡Šå¤„理数据
        int code = pIntent->getCode();
        // æµ‹è¯•
        // æµ‹è¯•
        if (code == FLOW_TEST) {
            AfxMessageBox(pIntent->getMsg());
        }
@@ -888,7 +911,7 @@
            return -1;
        }
        // æ‰¾åˆ°æŒ‡å®šçš„glass id,
        // æ‰¾åˆ°æŒ‡å®šçš„glass 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需要调用一次release
        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失败,code:%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失败,code:%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>正在请求单元<%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>主配方列表成功,正在等待数据.", 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>正在请求单元<%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>主配方参数列表成功,正在等待数据.", 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>找不到对应Glass, å…³è”工艺参数失败。CassetteSequenceNo:%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也要更新
        // å…³è”çš„Glass也要更新
        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失败,glassUpdateJobDataS返回%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的ID
        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失败,找不到对应的Glass.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, æ‰¾ä¸åˆ°å¯¹åº”glass", 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, æ‰¾ä¸åˆ°å¯¹åº”glass", 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或jobSequenceNo不匹配",
                    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那里
        // å½“前要存片,之前肯定有拔片,因此片子在Arm那里
        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为On时调用此函数
     * å¯èƒ½ä¼šå¤šæ¬¡é‡å¤è°ƒç”¨(根据扫描频率), æ³¨æ„é˜²å‘†
     */
    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;
    }
}
}
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: ä¸»é…æ–¹id
        // localRecipeId: æœ¬åœ°é…æ–¹id
        // unitNo: 0:local; Others:unit No
        int recipeParameterRequest(short masterRecipeId, short localRecipeId, short unitNo, ONSYNCINGSTATECHANGED block);
        // è§£æžé…æ–¹å‚数列表
        // è§£æžé…æ–¹å‚数列表
        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
        // èŽ·å–æŒ‡å®šçš„Slot
        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
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("至少选择一个Report"));
        return;
    }
    m_strName = name;
    m_strDesc = desc;
    m_rptIds.swap(selected);
    CDialogEx::OnOK();
}
SourceCode/Bond/Servo/CEventEditDlg.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
#pragma once
#include "afxdialogex.h"
#include <vector>
// äº‹ä»¶ç¼–辑对话框(新增/编辑共用,勾选Report)
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;
};
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数据口袋 ==========
    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精度:毫秒
            // å¦‚果需要精度更高,可能要使用其他时间单位,如std::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);
            }
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);
}
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);
};
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();
}
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()
};
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列表
    // å¿…须要实现的虚函数,在此初始化Pin列表
    void CLoadPort::initPins()
    {
        // åŠ å…¥Pin初始化代码
        // åŠ å…¥Pin初始化代码
        LOGD("<CLoadPort>initPins");
        addPin(SERVO::PinType::INPUT, _T("In"));
        addPin(SERVO::PinType::OUTPUT, _T("Out"));
    }
    // å¿…须要实现的虚函数,在此初始化Slot信息
    // å¿…须要实现的虚函数,在此初始化Slot信息
    void CLoadPort::initSlots()
    {
        for (int i = 0; i < SLOT_MAX; i++) {
@@ -351,7 +352,7 @@
        CEquipment::onTimer(nTimerid);
        // ä»Žé…ç½®è¯»å‡ºçš„enable,初始化时写给efem
        // ä»Žé…ç½®è¯»å‡ºçš„enable,初始化时写给efem
        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
        // å½“port状态为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 å†³ç­–(ProceedWithCarrier/ProceedWithSlotMap/CarrierRelease)
            // Host å†³ç­–入口:S3F17 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失败,code:%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失败,code:%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失败,code:%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失败,code:%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失败,code:%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失败,code:%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;
    }
}
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;
    };
}
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>正在初始化...");
        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>正在结束程序.");
        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和Bonder2都放了G1, Aligner也放了G1,
        // Bonder1和Bonder2需要的G2就过不来了
        // æœ€åŸºæœ¬çš„实现,可以G2和G2轮流搬送,但最好根据Bonder的需求来决定
        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,开始工作并切换到RUNNING状态
            lock();
            if (m_state == MASTERSTATE::STARTING) {
                // å‘送indexerOperationModeChange到各个机台,成功后切换到RUNNING状态
                // å¦åˆ™åˆ‡æ¢åˆ°MSERROR状态
                // å‘送indexerOperationModeChange到各个机台,成功后切换到RUNNING状态
                // å¦åˆ™åˆ‡æ¢åˆ°MSERROR状态
                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状态
                // æ£€æµ‹åˆ¤æ–­robot状态
                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、Bonder2、Fliper、VacuumBake、Aligner,统计G2和G1的数量, é…å¯¹ç»„æ•°, å¤šå‡ºçš„类型
                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 å—Glass到工艺列队!", nCount);
                    }
                }
                // 4) æœºå™¨äººçŠ¶æ€
                // 4) æœºå™¨äººçŠ¶æ€
                RMDATA& rmd = pEFEM->getRobotMonitoringData();
                if (rmd.status != ROBOT_STATUS::Idle && rmd.status != ROBOT_STATUS::Run) {
                    unlock(); continue;
                }
                // 5) æ­£åœ¨æ‰§è¡Œçš„ RobotTask å…ˆè®©å®ƒè·‘完一拍
                // 5) æ­£åœ¨æ‰§è¡Œçš„ 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>调度暂停:ControlJob/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 ç»Ÿä¸€å®šä¹‰ï¼ˆsecondary é»˜è®¤ 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 å†…部(Bake -> 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(restore)
                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
                            // ç»Ÿä¸€ï¼šqueue -> start -> setContext -> move queue→inProcess -> 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状态
                // æ£€æµ‹åˆ¤æ–­robot状态
                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工艺完成
                    // å¦‚果是搬送回从AOI搬送回Port, åˆ™glass工艺完成
                    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是否已经完成
                        // æ£€æŸ¥PJ是否已经完成
                        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是否已经完成
                            // æ£€æŸ¥CJ是否已经完成
                            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。status=%d, data=%lld", ((CEquipment*)pEquipment)->getName().c_str(), status);
            if (status == PORT_INUSE && m_pControlJob != nullptr) {
                CLoadPort* pPort = (CLoadPort*)pEquipment;
                auto pjs = m_pControlJob->getPjs();
@@ -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("已添加“Fliper”.");
        return pEquipment;
    }
@@ -1822,7 +2022,7 @@
        pEquipment->init();
        LOGE("已添加“VacuumBake”.");
        LOGE("已添加“VacuumBake”.");
        return pEquipment;
    }
@@ -1840,7 +2040,7 @@
        pEquipment->init();
        LOGE("已添加“Aligner”.");
        LOGE("已添加“Aligner”.");
        return pEquipment;
    }
@@ -1858,7 +2058,7 @@
        pEquipment->init();
        LOGE("已添加“EFEM(ROBOT)”.");
        LOGE("已添加“EFEM(ROBOT)”.");
        return pEquipment;
    }
@@ -1874,7 +2074,7 @@
        pEquipment->init();
        LOGE("已添加“ARM”.");
        LOGE("已添加“ARM”.");
        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("已添加“Aligner”.");
        return pEquipment;
    }
@@ -1949,7 +2149,7 @@
        addToEquipmentList(pEquipment);
        pEquipment->init();
        LOGE("已添加“Measurement”.");
        LOGE("已添加“Measurement”.");
        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);
                    // åŒæ—¶è§¦å‘一次子机台开始(示例:Bonder1, 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是否已经完成
                    // æ£€æŸ¥CJ是否已经完成
                    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(Host/本地勾选的期望加工槽位)
        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;
    }
}
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);
            }
        }
    };
}
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();
}
    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();
    }
}
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();
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("新增事件失败(可能ID重复或写入失败)"));
        }
    }
    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("编辑事件失败(可能写入失败)"));
        }
    }
}
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);
};
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);
}
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();
};
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)"));
        }
    }
}
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);
};
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;  // éšæœºå™ªå£°
    
    return baseValue + timeTrend + randomNoise;
}
}
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);
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;
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)
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);
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);
}
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;
};
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("新增报告失败(可能ID重复或文件写入失败)"));
        }
    }
    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("编辑报告失败(可能文件写入失败)"));
        }
    }
}
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);
};
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)"));
        }
    }
}
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);
};
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;
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();
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: åœ¨æ­¤æ·»åŠ æ¶ˆæ¯å¤„ç†ç¨‹åºä»£ç å’Œ/或调用默认值
    CDialogEx::OnTimer(nIDEvent);
}
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);
};
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;
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);
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
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 };
    };
}
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()
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;
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();
}
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;
};
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;
    }
}
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 "后烘烤A腔";
            if (unit == 1) return "冷却A";
            if (unit == 2) return "后烘烤B腔";
            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 "后烘烤A腔";
            if (slot == 1) return "冷却A";
            if (slot == 2) return "后烘烤B腔";
            if (slot == 3) return "冷却B";
        }
        if (eqid == EQ_ID_MEASUREMENT) {
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:
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();
}
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()
};
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());
}
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();
};
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;
}
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;
};
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();
}
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();
};
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;
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();
}
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;
};
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("正常") : _T("异常"));
        m_listClients.SetItemText(nItem, 3, CString(client.status.c_str()));
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
#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)
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();
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;
}
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 -- å¾—到的Rect
 * è¿”回是否成功
 */
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 -- å¾—到的Rect
 * è¿”回是否成功
 */
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 -- å¾—到的Rect
 * è¿”回是否成功
 */
BOOL CEqsGraphWnd::GetOutPinRect(EQITEM* pItem, int nPinIndex, LPRECT lpRect)
{
@@ -368,11 +384,11 @@
}
/*
 * å–å¾—Pin的Point
 * å–å¾—Pin的Point
 * pItem -- EQITEM
 * nPinIndex -- in pin索引
 * lpRect -- å¾—到的Rect
 * è¿”回是否成功
 * nPinIndex -- in pin索引
 * lpRect -- å¾—到的Rect
 * è¿”回是否成功
 */
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;
 * æ£€æµ‹åæ ‡ç‚¹æ‰€åœ¨çš„项
 * è¿”回项类型, å¦‚HT_ITEM, HT_PIN, HT_LINE
 * pItem - æ‰€åœ¨çš„EQITEM
 * pPin --所在的pin, å¦‚果在连线上,表示所属pin, 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
            // åœ¨Item
            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上
                    // åœ¨in 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
                        // åœ¨out pin
                        pPin = (PIN *)pPins->GetAt(j);
                        pItem = pTempItem;
                        nRet = HT_PIN;
                        break;
                    }
                    else {
                        // æ˜¯å¦åœ¨pin连接线上,即判断点是否在线上
                        // ç‚¹åˆ°ç›´çº¿çš„距离公式(先通过p1,p2用两点式求出直线的表达式,再套距离公式);abs()为取绝对值函数,sqrt()为开根号函数
                        // æ˜¯å¦åœ¨pin连接线上,即判断点是否在线上
                        // ç‚¹åˆ°ç›´çº¿çš„距离公式(先通过p1,p2用两点式求出直线的表达式,再套距离公式);abs()为取绝对值函数,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
 * ç»˜åˆ¶è™šçº¿æ¡†ï¼Œä»£è¡¨æ­£åœ¨æ‹–动的item
 */
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的Rect
 */
void CEqsGraphWnd::DrawPinConnectedLine(Gdiplus::Graphics *pGraphics, Gdiplus::Brush *pBrush, Gdiplus::Pen *pPen, LPPOINT lpPt1, LPPOINT lpPt2,
    LPRECT lpRect1, LPRECT lpRect2, PIN *pOwnerPin)
{
    // å¦‚果没有缓存线条的POINT,则先计算并缓存
    // å¦‚果没有缓存线条的POINT,则先计算并缓存
    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,窗口过程
 */
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、文本、pin å’ŒæŒ‡ç¤ºç¯
        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);
    }
}
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;
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);
    }
}
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();
};
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;
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;
    }
    // è§£é‡Šæ•°æ®ï¼Œå¾—到CProcessJob
    // å®¹é‡å‰ç½®æ£€æŸ¥ï¼šå½“前实现仅支持单批 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");
}
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);
    // æ‰§è¡Œä¸€æ®µæŒé”çš„代码块,用于保证 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
    // å–消/删除所有CollectionEvent
    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;
};
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()
{
}
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()
};
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(RX_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 è§£æžï¼ˆsoft 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);
                // æ–‡æ¡£æµç¨‹ï¼šProceedWithCarrier ä¹‹åŽè®¾å¤‡è¿›å…¥ Check SlotMap(WFH),
                // çœŸæ­£çš„“开始”由 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 è§£æžç»“构仅支持 {DATAID, CarrierAction, CarrierID, PTN},尚未实现上述扩展字段的解析/校验。
                // æœªæ¥è‹¥å®¢æˆ·ç¡®è®¤ SECS-II ç»“构,需要在 CHsmsPassive::replyCarrierAction() æ‰©å±•解析并在此处落库/校验。
                // ä»…在 CompareMapsBeforeProceeding å¯ç”¨ï¼ˆHost æ¨¡å¼ï¼‰ä¸‹å…è®¸æ­¤åŠ¨ä½œ
                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的map转换为list上传
                        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
        // ç»“批,保存ControlJob
        // 
    };
    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;
}
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 };
};
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];
        // åŽŸç¨‹åºè¦æ±‚PPID有子配方,先注释
        /*
        if (recipe.vecDeviceList.empty() || recipe.vecDeviceList.size() > 6){
            continue;
        }
        */
        m_listPPID.InsertItem(i, _T("")); // ç¬¬0列空白
@@ -386,13 +389,12 @@
void CPageRecipe::OnBnClickedButtonNew()
{
    // TODO: åœ¨æ­¤æ·»åŠ æŽ§ä»¶é€šçŸ¥å¤„ç†ç¨‹åºä»£ç 
    //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: åœ¨æ­¤æ·»åŠ æŽ§ä»¶é€šçŸ¥å¤„ç†ç¨‹åºä»£ç 
    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: åœ¨æ­¤æ·»åŠ æŽ§ä»¶é€šçŸ¥å¤„ç†ç¨‹åºä»£ç 
    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: åœ¨æ­¤æ·»åŠ æŽ§ä»¶é€šçŸ¥å¤„ç†ç¨‹åºä»£ç 
    if (IDYES != AfxMessageBox(_T("确定要删除全部配方记录吗?"), MB_YESNO | MB_ICONWARNING)) {
        return;
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);
    }
}
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");
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
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;
}
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);
};
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 æŽ§ä»¶ä¸­å¯ç”¨ä¸»é¢˜
    // æ¿€æ´»â€œWindows 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库
    // åˆå§‹åŒ–Rx库
    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: åœ¨æ­¤æ”¾ç½®å¤„理何时用
        //  â€œç¡®å®šâ€æ¥å…³é—­å¯¹è¯æ¡†çš„代码
    }
    else if (nResponse == IDCANCEL)
    {
        // TODO: åœ¨æ­¤æ”¾ç½®å¤„理何时用
        //  â€œå–消”来关闭对话框的代码
        // TODO: åœ¨æ­¤æ”¾ç½®å¤„理何时用
        //  â€œå–消”来关闭对话框的代码
    }
    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);
}
SourceCode/Bond/Servo/Servo.rc
Binary files differ
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>
</Project>
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>
</Project>
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>
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("正在加载历史缓存..."));
            m_pMyStatusbar->UpdateWindow();
        }
        LOGI("[BOOT][UI] before master.init, elapsed=%llu ms", (unsigned long long)(GetTickCount64() - boot_ui_begin));
    // ç›¸å½“于延时调用master的初始化
    theApp.m_model.m_master.init();
    theApp.m_model.loadPortParams();
        // ç›¸å½“于延时调用master的初始化
        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);
    }
    // åˆå§‹åŒ–master以后需要控件绑定数据
@@ -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("当前有机台发生错误,不能启动,请确认解决问题后再尝试重新启动!");
        }
@@ -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;
}
}
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);
};
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();
}
}
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());
}
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, ...);
};
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);
SourceCode/Bond/Servo/resource.h
Binary files differ
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
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);
SourceCode/Bond/x64/Debug/AlarmList.txt
@@ -225,3 +225,4 @@
9000,0,PauseEvent触发
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)
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)
SourceCode/Bond/x64/Debug/ReportList.txt
@@ -1,17 +1,23 @@
RPTID,(VID1,VID2,...)
RPTID,(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)
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,机器人R轴扭矩
2001,RbLAxisTorque,l2,机器人L轴扭矩
2002,RbZAxisTorque,l2,机器人Z轴扭矩
2003,RbTHAxisTorque,l2,机器人TH轴扭矩
2004,RbXAxisTorque,l2,机器人X轴扭矩
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使能:O:开启 1:屏蔽"
2024,CCDEnable,U2,"CCD使能:O:开启 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"
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
在上述文件截断后对比
SourceCode/Bond/x64/Debug/test.ini SourceCode/Bond/x64/Release/AlarmList.txt