72a8c79352717cf4ef7751f4e0f5d07ee1a65ab6..fc8d367963a16de61dfbc4a0ff34c78c91ec2cfe
2025-09-05 LAPTOP-SNT8I5JK\Boounion
Merge branch 'liuyang' into clh
fc8d36 对比 | 目录
2025-09-05 LAPTOP-SNT8I5JK\Boounion
1.修复加工好的玻璃放回原处仍被取走的问题。
55dea2 对比 | 目录
2025-09-05 LAPTOP-SNT8I5JK\Boounion
1.修复map取位错误造成port slot对应层数据出错问题;
e5e981 对比 | 目录
2025-09-04 mrDarker
1. 在配方数据库中添加配方参数元数据
a03e6a 对比 | 目录
2025-09-04 mrDarker
Merge branch 'clh' into liuyang
1c8098 对比 | 目录
2025-09-04 LAPTOP-SNT8I5JK\Boounion
1.机器配方参数的获取;
1efb83 对比 | 目录
2025-09-04 mrDarker
1. 配方列表中添加设备配方参数 2. 测试权限功能
f5d3ef 对比 | 目录
2025-09-04 mrDarker
1. 细分工程师用户权限(扩展制程工程师和设备工程师) 2. 配方修复选空白配方列表时,新建按钮不可点击的问题 3. 新添加双击配方列表事件
8e7305 对比 | 目录
2025-09-03 LAPTOP-SNT8I5JK\Boounion
1.配方列表和配方参数获取合并在线程中获取,并更新状态到对话框; 2.配方参数获取; 3.Bonder, AOI, 真空烘烤, 冷却烧烤配方参数解释。
96579a 对比 | 目录
2025-09-02 mrDarker
1. 配方数据库管理表里面添加ID(下发JobDataS) 2. 如果任务没有创建,那么配方是当前选择的(不是默认第一个)
8d492d 对比 | 目录
2025-09-02 LAPTOP-SNT8I5JK\Boounion
1.修复JobDataSy序列化,配方错位问题;
200a9f 对比 | 目录
2025-09-02 LAPTOP-SNT8I5JK\Boounion
1.修复加工完成的料放回去,当Port变为LoadReady状态时仍被取走的问题; 2.Port下料配置,每一片的物料类型设置修复;
968ccc 对比 | 目录
2025-09-01 mrDarker
1. JobDataS多了一次Last Glass Flag
9370dd 对比 | 目录
2025-09-01 mrDarker
1. 添加手动输入配方名称
d78ab8 对比 | 目录
已添加5个文件
已修改30个文件
3466 ■■■■■ 文件已修改
Document/EO2860AVA-101工艺参数(1).xlsx 补丁 | 查看 | 原始文档 | blame | 历史
Document/EO2860AVA-101工艺参数.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
Document/ESWIN_EAS_Bonder_Inline_Mapping_Address_v1.1.10.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CBakeCooling.cpp 89 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CBakeCooling.h 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CBonder.cpp 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CBonder.h 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEquipment.cpp 82 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEquipment.h 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CJobDataS.cpp 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CLoadPort.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMaster.cpp 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMeasurement.cpp 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMeasurement.h 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CParam.cpp 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CParam.h 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CRecipeList.cpp 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CRecipeList.h 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CRecipesManager.cpp 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CRecipesManager.h 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CRobotTask.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CVacuumBake.cpp 96 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CVacuumBake.h 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/PageRecipe.cpp 294 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/PageRecipe.h 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/PortConfigurationDlg.cpp 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/RecipeDeviceBindDlg.cpp 330 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/RecipeManager.cpp 873 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/RecipeManager.h 150 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj.filters 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ServoDlg.cpp 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/UserManager.cpp 688 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/UserManager.h 164 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/UserManagerDlg.cpp 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Document/EO2860AVA-101¹¤ÒÕ²ÎÊý(1).xlsx
Binary files differ
Document/EO2860AVA-101¹¤ÒÕ²ÎÊý.xlsx
Binary files differ
Document/ESWIN_EAS_Bonder_Inline_Mapping_Address_v1.1.10.xlsx
Binary files differ
SourceCode/Bond/Servo/CBakeCooling.cpp
@@ -207,33 +207,34 @@
        {
            // è¯·æ±‚配方参数
            //CEqWriteStep* pStep = new CEqWriteStep();
            //pStep->setName(STEP_EQ_RECIPE_PARAMETER_REQ);
            //pStep->setWriteSignalDev(0x967);
            //pStep->setDataDev(0x379b);
            //if (addStep(STEP_ID_RECIPE_PARAMETER_CMD_REPLY, pStep) != 0) {
            //    delete pStep;
            //}
            CEqWriteStep* pStep = new CEqWriteStep();
            pStep->setName(STEP_EQ_RECIPE_PARAMETER_REQ);
            pStep->setWriteSignalDev(0x967);
            pStep->setDataDev(0x24fb);
            if (addStep(STEP_ID_RECIPE_PARAMETER_CMD_REPLY, pStep) != 0) {
                delete pStep;
            }
        }
        {
            // recipe parameter report
            //CEqReadStep* pStep = new CEqReadStep(0x1aa54, 257 * 2,
            //    [&](void* pFrom, int code, const char* pszData, size_t size) -> int {
            //        if (code == ROK && pszData != nullptr && size > 0) {
            //            // æ­¤å¤„解释配方数据
            //            short ret = decodeRecipeParameterReport(pszData, size);
            //            pStep->setReturnCode(ret);
            //        }
            //        pStep->setReturnCode(MRLRC_OK);
            //        return -1;
            //    });
            //pStep->setName(STEP_EQ_RECIPE_PARAMETER);
            //pStep->setWriteSignalDev(0x94c);
            //pStep->setReturnDev(m_nIndex == 0 ? 0x126c : 0x1bbc);
            //if (addStep(STEP_ID_RECIPE_PARAMETER_REPORT, pStep) != 0) {
            //    delete pStep;
            //}
            CEqReadStep* pStep = new CEqReadStep(0x12a54, 257 * 2,
                [&](void* pFrom, int code, const char* pszData, size_t size) -> int {
                    CEqReadStep* pTmpStep = (CEqReadStep*)pFrom;
                    if (code == ROK && pszData != nullptr && size > 0) {
                        // æ­¤å¤„解释配方数据
                        short ret = decodeRecipeParameterReport(pszData, size);
                        pTmpStep->setReturnCode(ret);
                    }
                    pTmpStep->setReturnCode(MRLRC_OK);
                    return -1;
                });
            pStep->setName(STEP_EQ_RECIPE_PARAMETER);
            pStep->setWriteSignalDev(0x94c);
            pStep->setReturnDev(0x250c);
            if (addStep(STEP_ID_RECIPE_PARAMETER_REPORT, pStep) != 0) {
                delete pStep;
            }
        }
        // ä½¿ç”¨CEqReadStep替换CEqJobEventStep
@@ -422,4 +423,46 @@
        if (pGlass == nullptr) return false;
        return pGlass->isProcessed(m_nID, getSlotUnit(slot));
    }
    int CBakeCooling::parsingParams(const char* pszData, size_t size, std::vector<CParam>& params)
    {
        ASSERT(pszData);
        if (size < 250) return 0;
        int i = 0, v;
        // 1.A_腔烘烤时间
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("A_腔烘烤时间", "", "", v * 0.01f));
        i += 2;
        // 2.A_腔冷却时间
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("A_腔冷却时间", "", "", v * 0.01f));
        i += 2;
        // 3.B_腔烘烤时间
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("B_腔烘烤时间", "", "", v * 0.01f));
        i += 2;
        // 4.BB_腔冷却时间
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("B_腔冷却时间", "", "", v * 0.01f));
        i += 2;
        // 5.A_烘烤温度设定
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("A_烘烤温度设定", "", "", v * 0.1f));
        i += 2;
        // 6.B_烘烤温度设定
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("B_烘烤温度设定", "", "", v * 0.1f));
        i += 2;
        return (int)params.size();
    }
}
SourceCode/Bond/Servo/CBakeCooling.h
@@ -24,6 +24,7 @@
        virtual int getIndexerOperationModeBaseValue();
        virtual short getSlotUnit(short slotNo) { return slotNo % 2 == 1 ? 0 : 1; };
        virtual bool isSlotProcessed(int slot);
        virtual int parsingParams(const char* pszData, size_t size, std::vector<CParam>& parsms);
    };
}
SourceCode/Bond/Servo/CBonder.cpp
@@ -224,13 +224,14 @@
        {
            // recipe parameter report
            CEqReadStep* pStep = new CEqReadStep(m_nIndex == 0 ? 0xaa54 : 0xea54, 257 * 2,
                [&](void* pFrom, int code, const char* pszData, size_t size) -> int {
                [&, pStep](void* pFrom, int code, const char* pszData, size_t size) -> int {
                    CEqReadStep* pTmpStep = (CEqReadStep*)pFrom;
                    short ret = MRLRC_OK;
                    if (code == ROK && pszData != nullptr && size > 0) {
                        // æ­¤å¤„解释配方数据
                        short ret = decodeRecipeParameterReport(pszData, size);
                        pStep->setReturnCode(ret);
                        ret = decodeRecipeParameterReport(pszData, size);
                    }
                    pStep->setReturnCode(MRLRC_OK);
                    pTmpStep->setReturnCode(ret);
                    return -1;
                });
            pStep->setName(STEP_EQ_RECIPE_PARAMETER);
@@ -351,10 +352,10 @@
        {
            // FAC Data Report
            CEqReadStep* pStep = new CEqReadStep(0xA60E, 108 * 2,
            CEqReadStep* pStep = new CEqReadStep(m_nIndex == 0 ? 0xA60E : 0xE60E, 108 * 2,
                [&](void* pFrom, int code, const char* pszData, size_t size) -> int {
                    if (code == ROK && pszData != nullptr && size > 0) {
                        decodePanelDataReport((CStep*)pFrom, pszData, size);
                        decodeFacDataReport((CStep*)pFrom, pszData, size);
                    }
                    return -1;
                });
@@ -522,4 +523,123 @@
    {
        return m_nIndex == 0 ? 15000 : 20000;
    }
    int CBonder::parsingParams(const char* pszData, size_t size, std::vector<CParam>& params)
    {
        ASSERT(pszData);
        if (size < 250) return 0;
        int i = 0, v;
        // 1.校正对位延时
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("校正对位延时", "", "", v * 0.01f));
        i += 2;
        // 2.保压时间
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("保压时间", "", "", v * 0.01f));
        i += 2;
        // 3.腔体破真空延时
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("腔体破真空延时", "", "", v * 0.01f));
        i += 2;
        // 4.腔体分子泵启动延时
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("腔体分子泵启动延时", "", "", v * 0.1f));
        i += 2;
        // 5.腔体贴附抽真空延时
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("腔体贴附抽真空延时", "", "", v * 0.1f));
        i += 2;
        // 6.加热等待延时
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("加热等待延时", "", "", v * 0.1f));
        i += 2;
        // 7.气囊压力设定
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("气囊压力设定", "", "", v * 0.001f));
        i += 4;
        // 8.气囊加压速率
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("气囊加压速率", "", "", v * 0.001f));
        i += 4;
        // 9.气囊泄压速率
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("气囊泄压速率", "", "", v * 0.001f));
        i += 4;
        // 10.贴附压力上限
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("贴附压力上限", "", "", v * 0.1f));
        i += 4;
        // 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轴转矩速度设定", "", "", v * 0.001f));
        i += 4;
        // 12.上腔温度设定
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("上腔温度设定", "", "", v * 0.1f));
        i += 2;
        // 13.下腔温度设定
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("下腔温度设定", "", "", v * 0.1f));
        i += 2;
        // 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轴预贴合位速度", "", "", v * 0.001f));
        i += 4;
        // 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轴贴附位速度", "", "", v * 0.001f));
        i += 4;
        // 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上腔加热位间距", "", "", v * 0.001f));
        i += 4;
        // 17.上腔贴附位压入量
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("上腔贴附位压入量", "", "", v * 0.001f));
        i += 4;
        // 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轴破真空距离", "", "", v * 0.001f));
        i += 4;
        // 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破真空距离", "", "", v * 0.001f));
        i += 4;
        // 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加热位间距", "", "", v * 0.001f));
        i += 4;
        // 21.腔体真空泵真空规设定值
        params.push_back(CParam("腔体真空泵真空规设定值", "", "", (double)toFloat(&pszData[i])));
        i += 4;
        // 22.腔体分子泵到达设定值
        params.push_back(CParam("腔体分子泵到达设定值", "", "", (double)toFloat(&pszData[i])));
        i += 4;
        return (int)params.size();
    }
}
SourceCode/Bond/Servo/CBonder.h
@@ -24,6 +24,7 @@
        virtual int onProcessData(CProcessData* pProcessData);
        virtual int onProcessStateChanged(PROCESS_STATE state);
        virtual int getIndexerOperationModeBaseValue();
        virtual int parsingParams(const char* pszData, size_t size, std::vector<CParam>& parsms);
    public:
        void setIndex(unsigned int index);
SourceCode/Bond/Servo/CEquipment.cpp
@@ -496,6 +496,11 @@
        // ä¸»é…æ–¹ä¸ŠæŠ¥
        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);
        // CIM Mode
        CHECK_WRITE_STEP_SIGNAL(STEP_ID_CIMMODE_CHANGED_CMD_REPLY, pszData, size);
@@ -811,6 +816,16 @@
    CRecipeList* CEquipment::getRecipeList(int unitNo)
    {
        return m_recipesManager.getRecipeList(unitNo);
    }
    bool CEquipment::saveRecipeList(int unitNo, std::string& strFilepath)
    {
        return m_recipesManager.saveRecipeList(unitNo, strFilepath);
    }
    bool CEquipment::readRecipeList(int unitNo, std::string& strFilepath)
    {
        return m_recipesManager.readRecipeList(unitNo, strFilepath);
    }
    int CEquipment::recvIntent(CPin* pPin, CIntent* pIntent)
@@ -1189,28 +1204,39 @@
        return 0;
    }
    int CEquipment::recipeParameterRequest(short masterRecipeId, short localRecipeId, short unitNo)
    int CEquipment::recipeParameterRequest(short masterRecipeId, short localRecipeId, short unitNo, ONSYNCINGSTATECHANGED block)
    {
        SERVO::CEqWriteStep* pStep = (SERVO::CEqWriteStep*)getStepWithName(STEP_EQ_MASTER_RECIPE_LIST_REQ);
        LOGI("<CEquipment-%s>正在请求单元<%d>主配参数列表", m_strName.c_str(), unitNo);
        m_recipesManager.setOnSyncingStateChanged(block);
        if (m_recipesManager.syncing() != 0) {
            return -2;
        }
        SERVO::CEqWriteStep* pStep = (SERVO::CEqWriteStep*)getStepWithName(STEP_EQ_RECIPE_PARAMETER_REQ);
        if (pStep == nullptr) {
            return -1;
        }
        LOGI("<CEquipment-%s>正在请求单元<%d>主配方列表", m_strName.c_str(), unitNo);
        if (m_recipesManager.syncing() != 0) {
            return -2;
        }
        pStep->writeShort(unitNo, [&, unitNo](int code) -> int {
        char szBuffer[14 * 2] = {0};
        int index = 0;
        memcpy(&szBuffer[index], &masterRecipeId, sizeof(short));
        index += sizeof(short);
        memcpy(&szBuffer[index], &localRecipeId, sizeof(short));
        index += sizeof(short);
        memcpy(&szBuffer[index], &unitNo, sizeof(short));
        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();
                LOGI("<CEquipment-%s>请求单元<%d>主配方列表失败,code:%d", m_strName.c_str(), unitNo, code);
                LOGI("<CEquipment-%s>请求单元<%d>主配方参数列表失败,code:%d", m_strName.c_str(), unitNo, code);
            }
            return 0;
            });
        });
        return 0;
    }
@@ -1733,7 +1759,7 @@
        std::string strSvTimeRecord, strSvData;
        CToolUnits::convertString(&pszData[index], 8 * 2, strSvTimeRecord);
        index += 128 * 2;
        CToolUnits::convertString(&pszData[index], 640 * 2, strSvData);
        CToolUnits::convertString(&pszData[index], 100 * 2, strSvData);
        index += 256 * 2;
@@ -2075,4 +2101,38 @@
        return InspResult::NotInspected;
    }
    float CEquipment::toFloat(const char* pszAddr)
    {
        BYTE szBuffer[4];
        szBuffer[0] = pszAddr[0];
        szBuffer[1] = pszAddr[1];
        szBuffer[2] = pszAddr[2];
        szBuffer[3] = pszAddr[3];
        float f = 0.0;
        memcpy(&f, szBuffer, 4);
        return f;
    }
    int CEquipment::parsingParams(const char* pszData, size_t size, std::string& strOut)
    {
        std::vector<CParam> params;
        int nRet = parsingParams(pszData, size, params);
        if (nRet <= 0) return nRet;
        char szBuffer[256];
        for (auto p : params) {
            if(!strOut.empty()) strOut.append(",");
            if (p.getValueType() == PVT_INT) {
                sprintf_s(szBuffer, 256, "%s:%d", p.getName().c_str(), p.getIntValue());
            }
            else if (p.getValueType() == PVT_DOUBLE) {
                sprintf_s(szBuffer, 256, "%s:%f", p.getName().c_str(), p.getDoubleValue());
            }
            strOut.append(szBuffer);
        }
        return 0;
    };
}
SourceCode/Bond/Servo/CEquipment.h
@@ -35,6 +35,7 @@
#include "CProcessData.h"
#include "CPortStatusReport.h"
#include "CSlot.h"
#include "CParam.h"
namespace SERVO {
@@ -122,6 +123,8 @@
        std::vector<CPin*>& CEquipment::getInputPins();
        std::vector<CPin*>& CEquipment::getOutputPins();
        CRecipeList* getRecipeList(int unitNo);
        bool saveRecipeList(int unitNo, std::string& strFilepath);
        bool readRecipeList(int unitNo, std::string& strFilepath);
        virtual int recvIntent(CPin* pPin, CIntent* pIntent);
        virtual int fetchedOutJob(int port, CJobDataB* pJobDataB);
        virtual int storedJob(int port, CJobDataB* pJobDataB, short putSlot);
@@ -165,7 +168,11 @@
        // masterRecipeId: ä¸»é…æ–¹id
        // localRecipeId: æœ¬åœ°é…æ–¹id
        // unitNo: 0:local; Others:unit No
        int recipeParameterRequest(short masterRecipeId, short localRecipeId, short unitNo);
        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);
        // èŽ·å–æŒ‡å®šçš„Slot
        CSlot* getSlot(int index);
@@ -254,6 +261,7 @@
        int decodeJobProcessEndReport(CStep* pStep, const char* pszData, size_t size);
        BOOL compareJobData(CJobDataB* pJobDataB, CJobDataS* pJobDataS);
        void setProcessState(PROCESS_STATE state);
        float toFloat(const char* pszAddr);
    protected:
        BOOL m_bEnable;
SourceCode/Bond/Servo/CJobDataS.cpp
@@ -457,7 +457,7 @@
    int CJobDataS::serialize(char* pszBuffer, int nBufferSize)
    {
        if (nBufferSize < 256 * 2) return -1;
        if (nBufferSize < JOBDATAS_SIZE) return -1;
        int index = 0;
        memcpy(&pszBuffer[index], &m_nCassetteSequenceNo, sizeof(short));
@@ -513,9 +513,6 @@
        memcpy(&pszBuffer[index], &m_nFirstGlassFlag, sizeof(short));
        index += sizeof(short);
        memcpy(&pszBuffer[index], &m_nLastGlassFlag, sizeof(short));
        index += sizeof(short);
        memcpy(&pszBuffer[index], &m_nQTime[0], sizeof(short));
        index += sizeof(short);
@@ -561,7 +558,7 @@
        memcpy(&pszBuffer[index], &m_nProductJudge, sizeof(short));
        index += sizeof(short);
        return 256 * 2;
        return JOBDATAS_SIZE;
    }
    int CJobDataS::unserialize(const char* pszBuffer, int nBufferSize)
SourceCode/Bond/Servo/CLoadPort.cpp
@@ -1237,7 +1237,7 @@
        char szBuffer[64];
        for (int i = 0; i < SLOT_MAX; i++) {
            if (!m_slot[i].isEnable()) continue;
            if (!(map >> i) & 1) continue;
            if (((map >> i) & 1) == 0) continue;
            CJobDataS js;
            js.setCassetteSequenceNo(getNextCassetteSequenceNo());
SourceCode/Bond/Servo/CMaster.cpp
@@ -767,6 +767,13 @@
                        && pLoadPorts[s]->getPortStatus() == PORT_INUSE) {
                        m_pActiveRobotTask = createTransferTask(pLoadPorts[s], pAligner, primaryType, secondaryType, 1, m_bJobMode);
                        if (m_pActiveRobotTask != nullptr) {
                            CGlass* pGlass = (CGlass*)m_pActiveRobotTask->getContext();
                            if (pGlass->getBuddy() != nullptr) {
                                delete m_pActiveRobotTask;
                                m_pActiveRobotTask = nullptr;
                                continue;
                            }
                            pEFEM->setContext(m_pActiveRobotTask->getContext());
                            goto PORT_GET;
                        }
@@ -987,6 +994,12 @@
                        m_pActiveRobotTask = createTransferTask(pLoadPorts[s], pAligner, primaryType, secondaryType, m_bJobMode);
                        if (m_pActiveRobotTask != nullptr) {
                            CGlass* pGlass = (CGlass*)m_pActiveRobotTask->getContext();
                            if (pGlass->getBuddy() != nullptr) {
                                delete m_pActiveRobotTask;
                                m_pActiveRobotTask = nullptr;
                                continue;
                            }
                            pEFEM->setContext(pGlass);
                            pGlass->start();
                            bool bMoved = glassFromQueueToInPorcess(pGlass);
@@ -2493,7 +2506,9 @@
            m_pControlJob = new CControlJob();
            if (!CControlJob::deserialize(ifs, *m_pControlJob)) return false;
        }
        else {
            return false;
        }
        // è¯»å– ProcessJob åˆ—表
        uint32_t count = 0;
SourceCode/Bond/Servo/CMeasurement.cpp
@@ -207,33 +207,34 @@
        {
            // è¯·æ±‚配方参数
            //CEqWriteStep* pStep = new CEqWriteStep();
            //pStep->setName(STEP_EQ_RECIPE_PARAMETER_REQ);
            //pStep->setWriteSignalDev(0xf67);
            //pStep->setDataDev(0x379b);
            //if (addStep(STEP_ID_RECIPE_PARAMETER_CMD_REPLY, pStep) != 0) {
            //    delete pStep;
            //}
            CEqWriteStep* pStep = new CEqWriteStep();
            pStep->setName(STEP_EQ_RECIPE_PARAMETER_REQ);
            pStep->setWriteSignalDev(0xf67);
            pStep->setDataDev(0x379b);
            if (addStep(STEP_ID_RECIPE_PARAMETER_CMD_REPLY, pStep) != 0) {
                delete pStep;
            }
        }
        {
            // recipe parameter report
            //CEqReadStep* pStep = new CEqReadStep(0x1aa54, 257 * 2,
            //    [&](void* pFrom, int code, const char* pszData, size_t size) -> int {
            //        if (code == ROK && pszData != nullptr && size > 0) {
            //            // æ­¤å¤„解释配方数据
            //            short ret = decodeRecipeParameterReport(pszData, size);
            //            pStep->setReturnCode(ret);
            //        }
            //        pStep->setReturnCode(MRLRC_OK);
            //        return -1;
            //    });
            //pStep->setName(STEP_EQ_RECIPE_PARAMETER);
            //pStep->setWriteSignalDev(0xf4c);
            //pStep->setReturnDev(m_nIndex == 0 ? 0x126c : 0x1bbc);
            //if (addStep(STEP_ID_RECIPE_PARAMETER_REPORT, pStep) != 0) {
            //    delete pStep;
            //}
            CEqReadStep* pStep = new CEqReadStep(0x1aa54, 257 * 2,
                [&](void* pFrom, int code, const char* pszData, size_t size) -> int {
                    CEqReadStep* pTmpStep = (CEqReadStep*)pFrom;
                    if (code == ROK && pszData != nullptr && size > 0) {
                        // æ­¤å¤„解释配方数据
                        short ret = decodeRecipeParameterReport(pszData, size);
                        pTmpStep->setReturnCode(ret);
                    }
                    pTmpStep->setReturnCode(MRLRC_OK);
                    return -1;
                });
            pStep->setName(STEP_EQ_RECIPE_PARAMETER);
            pStep->setWriteSignalDev(0xf4c);
            pStep->setReturnDev(0x37ac);
            if (addStep(STEP_ID_RECIPE_PARAMETER_REPORT, pStep) != 0) {
                delete pStep;
            }
        }
        // ä½¿ç”¨CEqReadStep替换CEqJobEventStep
@@ -416,4 +417,24 @@
    {
        return 35000;
    }
    int CMeasurement::parsingParams(const char* pszData, size_t size, std::vector<CParam>& params)
    {
        ASSERT(pszData);
        if (size < 250) return 0;
        int i = 0, v;
        // 1.检测功能启用/禁用
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("检测功能启用/禁用", "", "", v));
        i += 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("检测速度", "", "", v * 0.001));
        i += 4;
        return (int)params.size();
    }
}
SourceCode/Bond/Servo/CMeasurement.h
@@ -22,6 +22,7 @@
        virtual void getAttributeVector(CAttributeVector& attrubutes);
        virtual int recvIntent(CPin* pPin, CIntent* pIntent);
        virtual int getIndexerOperationModeBaseValue();
        virtual int parsingParams(const char* pszData, size_t size, std::vector<CParam>& parsms);
    };
}
SourceCode/Bond/Servo/CParam.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,107 @@
#include "stdafx.h"
#include "CParam.h"
CParam::CParam()
{
    m_nValueType = PVT_INT;
    m_nValue = 0;
    m_fValue = 0.0;
}
CParam::CParam(const char* pszName, const char* pszId, const char* pszUnit, int value)
{
    m_nValueType = PVT_INT;
    m_nValue = value;
    m_fValue = 0.0;
    m_strId = pszId;
    m_strName = pszName;
    m_strUnit = pszUnit;
}
CParam::CParam(const char* pszName, const char* pszId, const char* pszUnit, double value)
{
    m_nValueType = PVT_DOUBLE;
    m_nValue = 0;
    m_fValue = value;
    m_strId = pszId;
    m_strName = pszName;
    m_strUnit = pszUnit;
}
CParam::~CParam()
{
}
std::string& CParam::getName()
{
    return m_strName;
}
std::string& CParam::getId()
{
    return m_strId;
}
std::string& CParam::getUnit()
{
    return m_strUnit;
}
int CParam::getValueType()
{
    return m_nValueType;
}
int CParam::getIntValue()
{
    return m_nValue;
}
void CParam::setIntValue(int value)
{
    m_nValue = value;
}
double CParam::getDoubleValue()
{
    return m_fValue;
}
void CParam::setDoubleValue(double value)
{
    m_fValue = value;
}
void CParam::Serialize(CArchive& ar)
{
    if (ar.IsStoring())
    {
        WriteString(ar, m_strName);
        WriteString(ar, m_strUnit);
        ar << m_nValueType;
        ar << m_nValue;
        ar << m_fValue;
    }
    else
    {
        ReadString(ar, m_strName);
        ReadString(ar, m_strUnit);
        ar >> m_nValueType;
        ar >> m_nValue;
        ar >> m_fValue;
    }
}
void CParam::ReadString(CArchive& ar, std::string& string)
{
    CString strTemp;
    ar >> strTemp;
    string = (LPTSTR)(LPCTSTR)strTemp;
}
void CParam::WriteString(CArchive& ar, std::string& string)
{
    CString strTemp = string.c_str();
    ar << strTemp;
}
SourceCode/Bond/Servo/CParam.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
#pragma once
#include <string>
#define PVT_INT        0
#define PVT_DOUBLE    1
class CParam
{
public:
    CParam();
    CParam(const char* pszName, const char* pszId, const char* pszUnit, int value);
    CParam(const char* pszName, const char* pszId, const char* pszUnit, double value);
    ~CParam();
public:
    std::string& getId();
    std::string& getName();
    std::string& getUnit();
    int getValueType();
    int getIntValue();
    void setIntValue(int value);
    double getDoubleValue();
    void setDoubleValue(double value);
    void Serialize(CArchive& ar);
private:
    void ReadString(CArchive& ar, std::string& string);
    void WriteString(CArchive& ar, std::string& string);
private:
    int m_nValueType;
    std::string m_strId;
    std::string m_strName;
    std::string m_strUnit;
    int m_nValue;
    double m_fValue;
};
SourceCode/Bond/Servo/CRecipeList.cpp
@@ -2,6 +2,7 @@
#include "CRecipeList.h"
#include "Common.h"
#include "ToolUnits.h"
#include <fstream>
namespace SERVO {
@@ -53,6 +54,8 @@
        }
        if (m_nCurrentGroupCount == m_nToatlGroupCount) {
            m_nToatlGroupCount = 0;
            m_nCurrentGroupCount = 0;
            return MRLRC_CURRENT_RECIPE_COMPLETE;
        }
@@ -77,10 +80,136 @@
        return m_ids;
    }
    std::unordered_map<short, std::vector<uint8_t>>& CRecipeList::getParamsRawData()
    {
        return m_paramsRawData;
    }
    void CRecipeList::reset()
    {
        m_nToatlGroupCount = 0;
        m_nCurrentGroupCount = 0;
        m_ids.clear();
        m_paramsRawData.clear();
    }
    void CRecipeList::reset2()
    {
        m_nToatlGroupCount = 0;
        m_nCurrentGroupCount = 0;
    }
    int CRecipeList::addParamsPacket(int totalCount, int totalGroup, int currentGroup,
        short unitId, short recipeId,
        const char* pszData, size_t size)
    {
        if (m_nToatlGroupCount == 0) m_nToatlGroupCount = totalGroup;
        if (m_nToatlGroupCount != totalGroup) {
            reset2();
            return MRLRC_GROUP_COUNT_NG;
        }
        if (currentGroup == 0) {
            reset2();
        }
        if (m_nCurrentGroupCount + 1 > currentGroup) {
            return MRLRC_DUPLICATION_GROUP_COUNT_NG;
        }
        if (m_nCurrentGroupCount + 1 < currentGroup) {
            return ORDER_BY_GROUP_COUNT_NG;
        }
        m_nCurrentGroupCount++;
        m_paramsRawData[recipeId].insert(m_paramsRawData[recipeId].end(), (uint8_t*)(pszData), (uint8_t*)(pszData) + size);
        if (m_nCurrentGroupCount == m_nToatlGroupCount) {
            // è§£é‡Šæ•°æ®å°±äº¤ç»™åº”用层吧
            reset2();
            return MRLRC_CURRENT_RECIPE_COMPLETE;
        }
        return MRLRC_CONTINUE;
    }
    // åºåˆ—化
    bool CRecipeList::serialize(const std::string& filename) const
    {
        std::ofstream ofs(filename, std::ios::binary);
        if (!ofs) return false;
        // å†™åŸºæœ¬æˆå‘˜
        ofs.write(reinterpret_cast<const char*>(&m_nUnitNo), sizeof(m_nUnitNo));
        ofs.write(reinterpret_cast<const char*>(&m_nToatlGroupCount), sizeof(m_nToatlGroupCount));
        ofs.write(reinterpret_cast<const char*>(&m_nCurrentGroupCount), sizeof(m_nCurrentGroupCount));
        // Ð´ m_ids
        size_t idsSize = m_ids.size();
        ofs.write(reinterpret_cast<const char*>(&idsSize), sizeof(idsSize));
        for (auto& kv : m_ids) {
            ofs.write(reinterpret_cast<const char*>(&kv.first), sizeof(kv.first));
            ofs.write(reinterpret_cast<const char*>(&kv.second), sizeof(kv.second));
        }
        // Ð´ m_paramsRawData
        size_t paramsSize = m_paramsRawData.size();
        ofs.write(reinterpret_cast<const char*>(&paramsSize), sizeof(paramsSize));
        for (auto& kv : m_paramsRawData) {
            // Ð´ key
            ofs.write(reinterpret_cast<const char*>(&kv.first), sizeof(kv.first));
            // å†™ vector å¤§å°
            size_t vecSize = kv.second.size();
            ofs.write(reinterpret_cast<const char*>(&vecSize), sizeof(vecSize));
            // å†™ vector å†…容
            if (!kv.second.empty()) {
                ofs.write(reinterpret_cast<const char*>(kv.second.data()), vecSize);
            }
        }
        return true;
    }
    // ååºåˆ—化
    bool CRecipeList::deserialize(const std::string& filename)
    {
        std::ifstream ifs(filename, std::ios::binary);
        if (!ifs) return false;
        reset(); // æ¸…空旧数据
        // è¯»åŸºæœ¬æˆå‘˜
        ifs.read(reinterpret_cast<char*>(&m_nUnitNo), sizeof(m_nUnitNo));
        ifs.read(reinterpret_cast<char*>(&m_nToatlGroupCount), sizeof(m_nToatlGroupCount));
        ifs.read(reinterpret_cast<char*>(&m_nCurrentGroupCount), sizeof(m_nCurrentGroupCount));
        // è¯» m_ids
        size_t idsSize = 0;
        ifs.read(reinterpret_cast<char*>(&idsSize), sizeof(idsSize));
        for (size_t i = 0; i < idsSize; ++i) {
            int key;
            short value;
            ifs.read(reinterpret_cast<char*>(&key), sizeof(key));
            ifs.read(reinterpret_cast<char*>(&value), sizeof(value));
            m_ids[key] = value;
        }
        // è¯» m_paramsRawData
        size_t paramsSize = 0;
        ifs.read(reinterpret_cast<char*>(&paramsSize), sizeof(paramsSize));
        for (size_t i = 0; i < paramsSize; ++i) {
            short key;
            size_t vecSize = 0;
            ifs.read(reinterpret_cast<char*>(&key), sizeof(key));
            ifs.read(reinterpret_cast<char*>(&vecSize), sizeof(vecSize));
            std::vector<uint8_t> buffer(vecSize);
            if (vecSize > 0) {
                ifs.read(reinterpret_cast<char*>(buffer.data()), vecSize);
            }
            m_paramsRawData[key] = std::move(buffer);
        }
        return true;
    }
}
SourceCode/Bond/Servo/CRecipeList.h
@@ -1,4 +1,4 @@
#pragma once
#pragma once
#include <map>
@@ -15,13 +15,25 @@
        int addRecipePacket(int totalCount,int totalGroup, int currentGroup, const char* pszData, size_t size);
        int addRecipe(int index, short id);
        std::map<int, short>& getIds();
        std::unordered_map<short, std::vector<uint8_t>>& getParamsRawData();
        void reset();
        void reset2();
        // æ·»åŠ å‚æ•°åŒ…
        int addParamsPacket(int totalCount, int totalGroup, int currentGroup,
            short unitId, short recipeId,
            const char* pszData, size_t size);
        // â­ æ–°å¢žåºåˆ—化和反序列化函数
        bool serialize(const std::string& filename) const;
        bool deserialize(const std::string& filename);
    private:
        int m_nUnitNo;
        int m_nToatlGroupCount;
        int m_nCurrentGroupCount;
        std::map<int, short> m_ids;
        std::unordered_map<short, std::vector<uint8_t>> m_paramsRawData;
    };
}
SourceCode/Bond/Servo/CRecipesManager.cpp
@@ -169,7 +169,7 @@
                }
                m_mapRecipes = m_mapRecipesTemp;
                m_mapRecipesTemp.clear();
                m_nSyncStatus = SS_COMPLETE;
                m_nSyncStatus = SS_LIST_COMPLETE;
                unlock();
                if (m_onSyncingStateChanged != nullptr) {
@@ -230,17 +230,14 @@
        // æ‰¾åˆ°å¯¹åº”CRecipeList, æ‰¾ä¸åˆ°åˆ™æ–°å»º
        /*
        // æ‰¾åˆ°å¯¹åº”CRecipeList, æ‰¾ä¸åˆ°åˆ™è¿”回NG
        lock();
        CRecipeList* pRecipeList = getRecipeListFromTemp(unitNo);
        CRecipeList* pRecipeList = getRecipeList(unitNo);
        if (pRecipeList == nullptr) {
            pRecipeList = new CRecipeList(unitNo);
            m_mapRecipesTemp[unitNo] = pRecipeList;
            unlock();
            return MRLRC_NG;
        }
        unlock();
        ASSERT(pRecipeList);
        */
        /*
         1: Create
@@ -248,7 +245,6 @@
         3: Delete
         4: Request from EAS
         */
        /*
        if (reportType == RT_CREATE) {
        }
@@ -259,26 +255,24 @@
        }
        else if (reportType == RT_REQUEST_FROM_EAS) {
            int nRet = pRecipeList->addRecipePacket(toatlGroupCount, currentGroupCount, pszIdsData, 250 * 2);
            int nRet = pRecipeList->addParamsPacket(totalParameterCount, toatlGroupCount, currentGroupCount,
                unitNo, localRecipeId,
                pszParameterData, 250 * 2);
            if (MRLRC_CURRENT_RECIPE_COMPLETE == nRet) {
                lock();
                if (m_nTotalMasterRecipeCount == m_mapRecipesTemp.size()) {
                    for (auto item : m_mapRecipes) {
                        delete item.second;
                    }
                    m_mapRecipes = m_mapRecipesTemp;
                    m_mapRecipesTemp.clear();
                    m_nSyncStatus = SS_COMPLETE;
                    unlock();
                    return MRLRC_OK;
                }
                m_nSyncStatus = SS_PARAMS_COMPLETE;
                unlock();
                if (m_onSyncingStateChanged != nullptr) {
                    m_onSyncingStateChanged(m_nSyncStatus);
                }
                return MRLRC_OK;
            }
            else if (MRLRC_CONTINUE == nRet) {
                return MRLRC_CONTINUE;
            }
            return nRet;
        }
        */
        return MRLRC_OK;
    }
@@ -297,6 +291,23 @@
        return iter->second;
    }
    bool CRecipesManager::saveRecipeList(int unitNo, std::string& strFilepath)
    {
        CRecipeList* pRecipeList = getRecipeList(unitNo);
        if (pRecipeList == nullptr) return false;
        return pRecipeList->serialize(strFilepath);
    }
    bool CRecipesManager::readRecipeList(int unitNo, std::string& strFilepath)
    {
        CRecipeList* pRecipeList = getRecipeList(unitNo);
        if (pRecipeList == nullptr) {
            pRecipeList = new CRecipeList();
            m_mapRecipes[unitNo] = pRecipeList;
        }
        return pRecipeList->deserialize(strFilepath);
    }
    void CRecipesManager::setOnSyncingStateChanged(ONSYNCINGSTATECHANGED block)
    {
        m_onSyncingStateChanged = block;
SourceCode/Bond/Servo/CRecipesManager.h
@@ -6,9 +6,10 @@
#define SS_NONE                    0
#define SS_SYNCING                1
#define SS_COMPLETE                2
#define SS_TIMEOUT                3
#define SS_FAILED                4
#define SS_LIST_COMPLETE        2
#define SS_PARAMS_COMPLETE        3
#define SS_TIMEOUT                4
#define SS_FAILED                5
namespace SERVO {
    typedef std::function<void(int state)> ONSYNCINGSTATECHANGED;
@@ -29,6 +30,8 @@
        short decodeRecipeParameterReport(const char* pszData, size_t size);
        CRecipeList* getRecipeListFromTemp(int unitNo);
        CRecipeList* getRecipeList(int unitNo);
        bool saveRecipeList(int unitNo, std::string& strFilepath);
        bool readRecipeList(int unitNo, std::string& strFilepath);
    public:
        inline void lock() { ::EnterCriticalSection(&m_cs); };
SourceCode/Bond/Servo/CRobotTask.cpp
@@ -220,7 +220,7 @@
                    LOGI(_T("RobotTask已下发到EFEM"));
                }
                else {
                    LOGI(_T("RobotTask已下发失败"));
                    LOGI(_T("RobotTask下发失败"));
                }
                return 0;
SourceCode/Bond/Servo/CVacuumBake.cpp
@@ -207,33 +207,34 @@
        {
            // è¯·æ±‚配方参数
            //CEqWriteStep* pStep = new CEqWriteStep();
            //pStep->setName(STEP_EQ_RECIPE_PARAMETER_REQ);
            //pStep->setWriteSignalDev(0xc67);
            //pStep->setDataDev(0x379b);
            //if (addStep(STEP_ID_RECIPE_PARAMETER_CMD_REPLY, pStep) != 0) {
            //    delete pStep;
            //}
            CEqWriteStep* pStep = new CEqWriteStep();
            pStep->setName(STEP_EQ_RECIPE_PARAMETER_REQ);
            pStep->setWriteSignalDev(0xc67);
            pStep->setDataDev(0x2e4b);
            if (addStep(STEP_ID_RECIPE_PARAMETER_CMD_REPLY, pStep) != 0) {
                delete pStep;
            }
        }
        {
            // recipe parameter report
            //CEqReadStep* pStep = new CEqReadStep(0x1aa54, 257 * 2,
            //    [&](void* pFrom, int code, const char* pszData, size_t size) -> int {
            //        if (code == ROK && pszData != nullptr && size > 0) {
            //            // æ­¤å¤„解释配方数据
            //            short ret = decodeRecipeParameterReport(pszData, size);
            //            pStep->setReturnCode(ret);
            //        }
            //        pStep->setReturnCode(MRLRC_OK);
            //        return -1;
            //    });
            //pStep->setName(STEP_EQ_RECIPE_PARAMETER);
            //pStep->setWriteSignalDev(0xc4c);
            //pStep->setReturnDev(m_nIndex == 0 ? 0x126c : 0x1bbc);
            //if (addStep(STEP_ID_RECIPE_PARAMETER_REPORT, pStep) != 0) {
            //    delete pStep;
            //}
            CEqReadStep* pStep = new CEqReadStep(0x16a54, 257 * 2,
                [&](void* pFrom, int code, const char* pszData, size_t size) -> int {
                    CEqReadStep* pTmpStep = (CEqReadStep*)pFrom;
                    if (code == ROK && pszData != nullptr && size > 0) {
                        // æ­¤å¤„解释配方数据
                        short ret = decodeRecipeParameterReport(pszData, size);
                        pTmpStep->setReturnCode(ret);
                    }
                    pTmpStep->setReturnCode(MRLRC_OK);
                    return -1;
                });
            pStep->setName(STEP_EQ_RECIPE_PARAMETER);
            pStep->setWriteSignalDev(0xc4c);
            pStep->setReturnDev(0x2ec);
            if (addStep(STEP_ID_RECIPE_PARAMETER_REPORT, pStep) != 0) {
                delete pStep;
            }
        }
        // ä½¿ç”¨CEqReadStep替换CEqJobEventStep
@@ -409,4 +410,53 @@
    {
        return 30000;
    }
    int CVacuumBake::parsingParams(const char* pszData, size_t size, std::vector<CParam>& params)
    {
        ASSERT(pszData);
        if (size < 250) return 0;
        int i = 0, v;
        // 1.A_腔加热时间
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("A_腔加热时间", "", "", v * 0.1f));
        i += 2;
        // 2.B_腔加热时间
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("B_腔加热时间", "", "", v * 0.1f));
        i += 2;
        // 3.A_腔破真空时间
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("A_腔破真空时间", "", "", v * 0.01f));
        i += 2;
        // 4.B_腔破真空时间
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("B_腔破真空时间", "", "", v * 0.01f));
        i += 2;
        // 5.A_腔真空到达值
        params.push_back(CParam("A_腔真空到达值", "", "", (double)toFloat(&pszData[i])));
        i += 4;
        // 6.B_腔真空到达值
        params.push_back(CParam("B_腔真空到达值", "", "", (double)toFloat(&pszData[i])));
        i += 4;
        // 7.A_腔温控表主控温度设定
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("A_腔温控表主控温度设定", "", "", v * 0.1f));
        i += 4;
        // 8.B_腔温控表主控温度设定
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("B_腔温控表主控温度设定", "", "", v * 0.1f));
        i += 4;
        return (int)params.size();
    }
}
SourceCode/Bond/Servo/CVacuumBake.h
@@ -22,7 +22,7 @@
        virtual void getAttributeVector(CAttributeVector& attrubutes);
        virtual int recvIntent(CPin* pPin, CIntent* pIntent);
        virtual int getIndexerOperationModeBaseValue();
        virtual int parsingParams(const char* pszData, size_t size, std::vector<CParam>& parsms);
    };
}
SourceCode/Bond/Servo/PageRecipe.cpp
@@ -6,7 +6,9 @@
#include "afxdialogex.h"
#include "PageRecipe.h"
#include "MsgDlg.h"
#include "InputDialog.h"
#include "RecipeDeviceBindDlg.h"
// CPageRecipe å¯¹è¯æ¡†
@@ -55,9 +57,9 @@
    CString strIniFile, strItem;
    strIniFile.Format(_T("%s\\configuration.ini"), (LPCTSTR)theApp.m_strAppDir);
    int width[] = { 0, 60, 150, 150 };
    int width[] = { 0, 60, 100, 100, 150 };
    for (int i = 0; i < 4; i++) {
    for (int i = 0; i < 5; i++) {
        strItem.Format(_T("Col_Device_%d_Width"), i);
        width[i] = GetPrivateProfileInt(_T("PageRecipeListCtrl"), strItem, width[i], strIniFile);
    }
@@ -66,6 +68,7 @@
    m_listPPID.InsertColumn(1, _T("No."), LVCFMT_CENTER, width[1]);
    m_listPPID.InsertColumn(2, _T("Recipe ID"), LVCFMT_CENTER, width[2]);
    m_listPPID.InsertColumn(3, _T("Recipe åç§°"), LVCFMT_CENTER, width[3]);
    m_listPPID.InsertColumn(4, _T("Recipe å‚æ•°"), LVCFMT_CENTER, width[4]);
}
void CPageRecipe::UpdateRecipeByPPID(const CString& strPPID)
@@ -146,8 +149,18 @@
        m_listPPID.SetItemText(i, 2, CA2T(recipe.strPPID.c_str()));
        for (int j = 0; j < recipe.vecDeviceList.size(); j++){
            int nRecipeID = recipe.vecDeviceList.at(j).nRecipeID;
            std::string strDeviceName = recipe.vecDeviceList.at(j).strDeviceName;
            std::string strRecipeName = RecipeManager::getInstance().getDeviceRecipeName(strDeviceName, nRecipeID);
            CString str;
            str.Format(_T("%d"), recipe.vecDeviceList.at(j).nRecipeID);
            if (strRecipeName.empty()) {
                str.Format(_T("%d"), recipe.vecDeviceList.at(j).nRecipeID);
            }
            else {
                str.Format(_T("%s"), CA2T(strRecipeName.c_str()));
            }
            m_listPPID.SetItemText(i, j + 3, str);
        }
@@ -160,8 +173,9 @@
    m_listPPID.SetColumnWidth(nColCount - 1, LVSCW_AUTOSIZE_USEHEADER);
}
void CPageRecipe::FillRecipeListToListCtrl(SERVO::CRecipeList* pList)
void CPageRecipe::FillRecipeListToListCtrl(SERVO::CEquipment* pEq)
{
    SERVO::CRecipeList* pRecipeList = pEq->getRecipeList(0);
    CListCtrl* pListCtrl = (CListCtrl*)GetDlgItem(IDC_LIST_PPID);
    if (pListCtrl == nullptr || !::IsWindow(pListCtrl->m_hWnd)) {
        return;
@@ -169,16 +183,33 @@
    // æ¸…空当前CListCtrl中的所有项
    pListCtrl->DeleteAllItems();
    if (pList == nullptr) {
    if (pRecipeList == nullptr) {
        return;
    }
    // éåŽ†æ•°æ®å¹¶æ’å…¥åˆ°CListCtrl中
    std::map<int, short>& ids = pList->getIds();
    auto& mgr = RecipeManager::getInstance();
    std::map<int, short>& ids = pRecipeList->getIds();
    auto rawDatas = pRecipeList->getParamsRawData();
    for (auto item : ids) {
        int index = m_listPPID.InsertItem(m_listPPID.GetItemCount(), _T(""));
        m_listPPID.SetItemText(index, 1, std::to_string(item.first).c_str());
        m_listPPID.SetItemText(index, 2, std::to_string(item.second).c_str());
        std::string strRecipeName = mgr.getDeviceRecipeName(pEq->getName(), item.second);
        m_listPPID.SetItemText(index, 3, strRecipeName.c_str());
        std::string strDescription;
        auto iter = rawDatas.find(item.second);
        if (iter != rawDatas.end()) {
            pEq->parsingParams((const char*)iter->second.data(), iter->second.size(), strDescription);
            m_listPPID.SetItemText(index, 4, strDescription.c_str());
        }
        if (strRecipeName.empty()) {
            strRecipeName = std::to_string(item.second);
            mgr.addDeviceRecipe(pEq->getName(), item.second, strRecipeName, strDescription);
        }
    }
    // èŽ·å–åˆ—æ•°
@@ -203,7 +234,8 @@
    ON_BN_CLICKED(IDC_BUTTON_DELETE, &CPageRecipe::OnBnClickedButtonDelete)
    ON_BN_CLICKED(IDC_BUTTON_DELETE_ALL, &CPageRecipe::OnBnClickedButtonDeleteAll)
    ON_BN_CLICKED(IDC_BUTTON_REFRESH, &CPageRecipe::OnBnClickedButtonRefresh)
    ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_PPID, &CPageRecipe::OnLvnItemChangedListPPID)
    ON_NOTIFY(NM_CLICK, IDC_LIST_PPID, &CPageRecipe::OnClickListPPID)
    ON_NOTIFY(NM_DBLCLK, IDC_LIST_PPID, &CPageRecipe::OnDblclkListPPID)
    ON_CBN_SELCHANGE(IDC_COMBO_EQUIPMENT, &CPageRecipe::OnCbnSelchangeComboEquipment)
END_MESSAGE_MAP()
@@ -338,8 +370,12 @@
                    pEq[i] == nullptr ? _T("Master") : pEq[i]->getName().c_str());
                pComboBox->SetItemDataPtr(i, pEq[i]);
                // è¯»å–回来
                char szBuffer[_MAX_PATH];
                if (pEq[i]) {
                    pEq[i]->masterRecipeListRequest(0, nullptr);
                    sprintf_s(szBuffer, _MAX_PATH, "%s\\Recipe\\EQ%d_Unit0.recipelist", (LPTSTR)(LPCTSTR)theApp.m_strAppDir, pEq[i]->getID());
                    std::string strFilepath(szBuffer);
                    pEq[i]->readRecipeList(0, strFilepath);
                }
            }
            pComboBox->SetCurSel(0);
@@ -416,15 +452,41 @@
void CPageRecipe::OnBnClickedButtonModify()
{
    // TODO: åœ¨æ­¤æ·»åŠ æŽ§ä»¶é€šçŸ¥å¤„ç†ç¨‹åºä»£ç 
    CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_EQUIPMENT);
    if (pComboBox == nullptr || !::IsWindow(pComboBox->m_hWnd)) {
        return;
    }
    POSITION pos = m_listPPID.GetFirstSelectedItemPosition();
    if (!pos) {
        AfxMessageBox(_T("请先选择一条配方记录进行修改!"));
        return;
    }
    int nSel = m_listPPID.GetNextSelectedItem(pos);
    CString strPPID = m_listPPID.GetItemText(nSel, 2);
    UpdateRecipeByPPID(strPPID);
    int nLine = m_listPPID.GetNextSelectedItem(pos);
    CString strID = m_listPPID.GetItemText(nLine, 2);
    int nSel = pComboBox->GetCurSel();
    SERVO::CEquipment* pEq = (SERVO::CEquipment*)pComboBox->GetItemDataPtr(nSel);
    if (pEq == nullptr) {
        UpdateRecipeByPPID(strID);
    }
    else {
        CInputDialog dlg(_T("修改配方名称"), _T("请输入配方名称:"));
        if (dlg.DoModal() != IDOK) {
            return;
        }
        CString strText = dlg.GetInputText();
        if (strText.IsEmpty()) {
            AfxMessageBox(_T("配方名称不能为空!"));
            return;
        }
        if (RecipeManager::getInstance().updateDeviceRecipeName(pEq->getName(), _ttoi(strID), std::string(CT2A(strText)))) {
            m_listPPID.SetItemText(nLine, 3, strText);
        }
    }
}
void CPageRecipe::OnBnClickedButtonDelete()
@@ -486,61 +548,69 @@
        FillDataToListCtrl(vecData);
    }
    else {
        // enable port
        CMsgDlg msgDlg("请等待", "正在获取配方...");
        pEq->masterRecipeListRequest(0, [&, pEq](int status) -> void {
            if (status == SS_FAILED || status == SS_TIMEOUT) {
                CString strMsg;
                strMsg.Format(status == SS_FAILED ? _T("获取配方失败!") : _T("获取配方超时!"));
                msgDlg.DelayClose(3000);
                msgDlg.SetIcon(MSG_BOX_ERROR);
                msgDlg.SetTitle(_T("操作失败"));
                msgDlg.SetMessage((LPTSTR)(LPCTSTR)strMsg);
                msgDlg.SetMarquee(FALSE, 0);
                msgDlg.SetCompleteCode(-1);
            }
            else if (status == SS_COMPLETE) {
                CString strMsg;
                strMsg.Format(_T("获取配方完成!"));
                msgDlg.DelayClose(3000);
                msgDlg.SetIcon(MSG_BOX_SUCCEED);
                msgDlg.SetTitle(_T("操作成功"));
                msgDlg.SetMessage((LPTSTR)(LPCTSTR)strMsg);
                msgDlg.SetMarquee(FALSE, 0);
                msgDlg.SetCompleteCode(0);
                SERVO::CRecipeList* pRecipeList = pEq->getRecipeList(0);
                FillRecipeListToListCtrl(pRecipeList);
            }
        });
        // èŽ·å–é…æ–¹åˆ—è¡¨
        CMsgDlg msgDlg("请等待", "正在获取配方列表...");
        msgDlg.SetData((DWORD_PTR)this);
        msgDlg.SetDataEx((DWORD_PTR)pEq);
        msgDlg.BeginThread(SyncThreadFunction);
        msgDlg.DoModal();
    }
}
void CPageRecipe::OnLvnItemChangedListPPID(NMHDR* pNMHDR, LRESULT* pResult)
void CPageRecipe::OnClickListPPID(NMHDR* pNMHDR, LRESULT* pResult)
{
    LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
    LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
    // TODO: åœ¨æ­¤æ·»åŠ æŽ§ä»¶é€šçŸ¥å¤„ç†ç¨‹åºä»£ç 
    *pResult = 0;
    CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_EQUIPMENT);
    int nEqSel = pComboBox->GetCurSel();
    int selectedCount = ListView_GetSelectedCount(m_listPPID.GetSafeHwnd());
    if (pComboBox == nullptr) {
        return;
    }
    GetDlgItem(IDC_BUTTON_NEW)->EnableWindow(nEqSel == 0 && selectedCount > 0);
    GetDlgItem(IDC_BUTTON_MODIFY)->EnableWindow(nEqSel == 0 && selectedCount > 0);
    GetDlgItem(IDC_BUTTON_DELETE)->EnableWindow(nEqSel == 0 && selectedCount > 0);
    GetDlgItem(IDC_BUTTON_DELETE_ALL)->EnableWindow(nEqSel == 0 && selectedCount > 0);
    int nItem = pNMItemActivate->iItem;
    int nEqSel = pComboBox->GetCurSel();
    GetDlgItem(IDC_BUTTON_NEW)->EnableWindow(nEqSel == 0);
    GetDlgItem(IDC_BUTTON_MODIFY)->EnableWindow(nItem != -1);
    GetDlgItem(IDC_BUTTON_DELETE)->EnableWindow(nEqSel == 0 && nItem != -1);
    GetDlgItem(IDC_BUTTON_DELETE_ALL)->EnableWindow(nEqSel == 0 && nItem != -1);
}
void CPageRecipe::OnDblclkListPPID(NMHDR* pNMHDR, LRESULT* pResult)
{
    LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
    // TODO: åœ¨æ­¤æ·»åŠ æŽ§ä»¶é€šçŸ¥å¤„ç†ç¨‹åºä»£ç 
    *pResult = 0;
    int nItem = pNMItemActivate->iItem;
    if (nItem < 0) {
        return;
    }
    CString strText = m_listPPID.GetItemText(nItem, 2);
    CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_EQUIPMENT);
    int nEqSel = pComboBox->GetCurSel();
    if (nEqSel == CB_ERR) {
        return;
    }
    SERVO::CEquipment* pEq = (SERVO::CEquipment*)pComboBox->GetItemDataPtr(nEqSel);
    if (pEq == nullptr) {
        return;
    }
}
void CPageRecipe::OnCbnSelchangeComboEquipment()
{
    CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_EQUIPMENT);
    int nEqSel = pComboBox->GetCurSel();
    int selectedCount = ListView_GetSelectedCount(m_listPPID.GetSafeHwnd());
    int nItem = ListView_GetSelectedCount(m_listPPID.GetSafeHwnd());
    GetDlgItem(IDC_BUTTON_NEW)->EnableWindow(nEqSel == 0 && selectedCount > 0);
    GetDlgItem(IDC_BUTTON_MODIFY)->EnableWindow(nEqSel == 0 && selectedCount > 0);
    GetDlgItem(IDC_BUTTON_DELETE)->EnableWindow(nEqSel == 0 && selectedCount > 0);
    GetDlgItem(IDC_BUTTON_DELETE_ALL)->EnableWindow(nEqSel == 0 && selectedCount > 0);
    GetDlgItem(IDC_BUTTON_NEW)->EnableWindow(nEqSel == 0);
    GetDlgItem(IDC_BUTTON_MODIFY)->EnableWindow(nEqSel == 0 && nItem != -1);
    GetDlgItem(IDC_BUTTON_DELETE)->EnableWindow(nEqSel == 0 && nItem != -1);
    GetDlgItem(IDC_BUTTON_DELETE_ALL)->EnableWindow(nEqSel == 0 && nItem != -1);
    GetDlgItem(IDC_EDIT_KEYWORD)->EnableWindow(nEqSel == 0);
    GetDlgItem(IDC_BUTTON_SEARCH)->EnableWindow(nEqSel == 0);
@@ -552,7 +622,127 @@
    }
    else {
        InitListCtrlHeaderForDevice();
        SERVO::CRecipeList* pRecipeList = pEq->getRecipeList(0);
        FillRecipeListToListCtrl(pRecipeList);
        FillRecipeListToListCtrl(pEq);
    }
}
UINT CPageRecipe::SyncThreadFunction(LPVOID lpvData)
{
    CMsgDlg* pMsgDlg = (CMsgDlg*)lpvData;
    CPageRecipe* pPageRecipe = (CPageRecipe*)pMsgDlg->GetData();
    return pPageRecipe->SyncThreadFunctionInner(pMsgDlg);
}
UINT CPageRecipe::SyncThreadFunctionInner(CMsgDlg* pMsgDlg)
{
    SERVO::CEquipment* pEq = (SERVO::CEquipment*)pMsgDlg->GetDataEx();
    HANDLE hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
    int nStep = 0;
    // å‡†å¤‡é…æ–¹è·¯å¾„
    char szBuffer[_MAX_PATH];
    sprintf_s(szBuffer, _MAX_PATH, "%s\\Recipe\\EQ%d_Unit0.recipelist", (LPTSTR)(LPCTSTR)theApp.m_strAppDir, pEq->getID());
    std::string strFilepath(szBuffer);
    pEq->masterRecipeListRequest(0, [&, pEq, pMsgDlg, hEvent](int status) -> void {
        Sleep(300);
        if (status == SS_FAILED || status == SS_TIMEOUT) {
            CString strMsg;
            strMsg.Format(status == SS_FAILED ? _T("获取配方列表失败!") : _T("获取配方列表超时!"));
            pMsgDlg->SetIcon(MSG_BOX_ERROR);
            pMsgDlg->SetTitle(_T("操作失败"));
            pMsgDlg->SetMessage((LPTSTR)(LPCTSTR)strMsg);
            SetEvent(hEvent);
        }
        else if (status == SS_LIST_COMPLETE) {
            CString strMsg;
            strMsg.Format(_T("获取配方列表完成!"));
            pMsgDlg->SetTitle(_T("操作成功"));
            pMsgDlg->SetMessage((LPTSTR)(LPCTSTR)strMsg);
            SERVO::CRecipeList* pRecipeList = pEq->getRecipeList(0);
            if (pRecipeList != nullptr && !pRecipeList->getIds().empty()) {
                nStep = 1;
            }
            SetEvent(hEvent);
        }
    });
    ::WaitForSingleObject(hEvent, INFINITE);
    if (nStep != 1) {
        pEq->saveRecipeList(0, strFilepath);
        pMsgDlg->SetIcon(MSG_BOX_SUCCEED);
        pMsgDlg->SetMarquee(FALSE, 0);
        pMsgDlg->SetCompleteCode(-1);
        pMsgDlg->DelayClose(3000);
    }
    ResetEvent(hEvent);
    // å‚数列表
    if (nStep == 1) {
        SERVO::CRecipeList* pRecipeList = pEq->getRecipeList(0);
        ASSERT(pRecipeList);
        auto& ids = pRecipeList->getIds();
        pMsgDlg->SetTitle(_T("正在获取参数"));
        for (auto item : ids) {
            int recipeId = item.second;
            CString strMsg;
            strMsg.Format(_T("正在获取配方 %d å‚æ•°..."), item.second);
            pMsgDlg->SetMessage((LPTSTR)(LPCTSTR)strMsg);
            pEq->recipeParameterRequest(0, recipeId, 0, [&, pEq, pMsgDlg, recipeId, hEvent](int status) -> void {
                Sleep(500);
                if (status == SS_FAILED || status == SS_TIMEOUT) {
                    CString strMsg;
                    strMsg.Format(status == SS_FAILED ? _T("获取配方 %d å‚数失败!") : _T("获取配方 %d å‚数超时!"), recipeId);
                    pMsgDlg->SetMessage((LPTSTR)(LPCTSTR)strMsg);
                    Sleep(30);
                    SetEvent(hEvent);
                }
                else if (status == SS_PARAMS_COMPLETE) {
                    CString strMsg;
                    strMsg.Format(_T("获取配方 %d å‚数完成!"), item.second);
                    pMsgDlg->SetMessage((LPTSTR)(LPCTSTR)strMsg);
                    Sleep(30);
                    SetEvent(hEvent);
                }
                });
            ::WaitForSingleObject(hEvent, INFINITE);
            ResetEvent(hEvent);
        }
        pEq->saveRecipeList(0, strFilepath);
        pMsgDlg->SetIcon(MSG_BOX_SUCCEED);
        pMsgDlg->SetTitle(_T("操作完成"));
        pMsgDlg->SetCompleteCode(0);
        pMsgDlg->SetMarquee(FALSE, 0);
        pMsgDlg->DelayClose(3000);
    };
    FillRecipeListToListCtrl(pEq);
    CloseHandle(hEvent);
    // åœ¨æ­¤æ‰“印配方参数以便核对数据
    SERVO::CRecipeList* pRecipeList = pEq->getRecipeList(0);
    ASSERT(pRecipeList);
    auto rawDatas = pRecipeList->getParamsRawData();
    for (auto item : rawDatas) {
        TRACE("================= é…æ–¹ %d\n", item.first);
        std::vector<CParam> params;
        pEq->parsingParams((const char*)item.second.data(), item.second.size(), params);
        for (auto p : params) {
            if (p.getValueType() == PVT_INT) {
                TRACE("%s: %d\n", p.getName().c_str(), p.getIntValue());
            }
            else if (p.getValueType() == PVT_DOUBLE) {
                TRACE("%s: %f\n", p.getName().c_str(), p.getDoubleValue());
            }
        }
    }
    return 0;
}
SourceCode/Bond/Servo/PageRecipe.h
@@ -2,6 +2,8 @@
#include "afxdialogex.h"
#include "RecipeManager.h"
#include "ListCtrlEx.h"
#include "MsgDlg.h"
// CPageRecipe å¯¹è¯æ¡†
@@ -13,12 +15,16 @@
    CPageRecipe(CWnd* pParent = nullptr);   // æ ‡å‡†æž„造函数
    virtual ~CPageRecipe();
public:
    static UINT SyncThreadFunction(LPVOID lpvData);
    UINT SyncThreadFunctionInner(CMsgDlg* pMsgDlg);
private:
    void InitListCtrlHeaderForMaster();
    void InitListCtrlHeaderForDevice();
    void UpdateRecipeByPPID(const CString& strPPID);
    void FillDataToListCtrl(const std::vector<RecipeInfo>& vecRecipe);
    void FillRecipeListToListCtrl(SERVO::CRecipeList* pList);
    void FillRecipeListToListCtrl(SERVO::CEquipment* pEq);
// å¯¹è¯æ¡†æ•°æ®
#ifdef AFX_DESIGN_TIME
@@ -37,10 +43,11 @@
    afx_msg void OnBnClickedButtonDelete();
    afx_msg void OnBnClickedButtonDeleteAll();
    afx_msg void OnBnClickedButtonRefresh();
    afx_msg void OnLvnItemChangedListPPID(NMHDR* pNMHDR, LRESULT* pResult);
    afx_msg void OnClickListPPID(NMHDR* pNMHDR, LRESULT* pResult);
    afx_msg void OnDblclkListPPID(NMHDR* pNMHDR, LRESULT* pResult);
    afx_msg void OnCbnSelchangeComboEquipment();
    DECLARE_MESSAGE_MAP()
private:
    CListCtrlEx m_listPPID;
};
};
SourceCode/Bond/Servo/PortConfigurationDlg.cpp
@@ -11,8 +11,6 @@
#include "RecipeManager.h"
#include "ServoCommo.h"
#define CHECKBOX_ALL_ID        0x1234
// CPortConfigurationDlg å¯¹è¯æ¡†
@@ -102,11 +100,26 @@
        // å›žå¡« Job ä¿¡æ¯ï¼ˆåªå–第一个有效 Glass)
        if (!bJobInfoSet && pGlass) {
            SERVO::CJobDataS* pJS = pGlass->getJobDataS();
            if (pJS) {
            if (pJS) {
                int nRecipeID = pJobDataS->getMasterRecipe();
                std::string strRecipeName = RecipeManager::getInstance().getPPIDById(nRecipeID);
                SetDlgItemText(IDC_EDIT_LOTID, CString(pJS->getLotId().c_str()));
                SetDlgItemText(IDC_EDIT_PRODUCTID, CString(pJS->getProductId().c_str()));
                SetDlgItemText(IDC_EDIT_OPERATIONID, CString(pJS->getOperationId().c_str()));
                m_comboMaterialsType.SetCurSel(pJS->getMaterialsType() - 1);
                if (!strRecipeName.empty()) {
                    CString csRecipeName(strRecipeName.c_str());
                    int nIndex = m_comboRecipe.FindStringExact(-1, csRecipeName);
                    if (nIndex != CB_ERR) {
                        m_comboRecipe.SetCurSel(nIndex);
                    }
                    else {
                        AfxMessageBox(_T("当前配方在系统中不存在,请重新选择!"), MB_ICONWARNING);
                        m_comboRecipe.SetCurSel(0);
                    }
                }
                bJobInfoSet = true;
            }
        }
@@ -252,7 +265,6 @@
        SetWindowText(_T("Port Configuration"));
    }
    // Porcess Start / Process Cancel æŒ‰é’®çŠ¶æ€
    GetDlgItem(IDC_BUTTON_PROCESS_START)->EnableWindow(FALSE);
    GetDlgItem(IDC_BUTTON_PROCESS_CANCEL)->EnableWindow(FALSE);
@@ -299,6 +311,9 @@
        m_comboRecipe.GetLBText(selRecipe, str);
        config.strRecipe = CT2A(str.GetString());
    }
    int nRecipeID = RecipeManager::getInstance().getIdByPPID(config.strRecipe);
    RecipeInfo stRecipeInfo = RecipeManager::getInstance().getRecipeByPPID(config.strRecipe);
    std::vector<DeviceRecipe> vecRecipeInfo = stRecipeInfo.vecDeviceList;
    // èŽ·å– Material Type ç´¢å¼•(索引从 0 å¼€å§‹ï¼Œå¯¹åº”枚举从 1 å¼€å§‹ï¼‰
    //int selMaterial = m_comboMaterialsType.GetCurSel();
@@ -342,16 +357,14 @@
            CGridCellCombo* pCombo = dynamic_cast<CGridCellCombo*>(m_wndGrid.GetCell(i, 2));
            ASSERT(pCheck && pCombo);
            pGlass->setScheduledForProcessing(pCheck->GetCheck());
            /*pGlass->setType(static_cast<SERVO::MaterialsType>(config.nMaterialType));*/
            pGlass->setType(static_cast<SERVO::MaterialsType>(nMaterialType));
            SERVO::CJobDataS* pJobDataS = pGlass->getJobDataS();
            pJobDataS->setLotId(config.strLotID.c_str());
            pJobDataS->setProductId(config.strProductID.c_str());
            pJobDataS->setOperationId(config.strOperationID.c_str());
            pJobDataS->setMaterialsType(nMaterialType);
            RecipeInfo stRecipeInfo = RecipeManager::getInstance().getRecipeByPPID(config.strRecipe);
            std::vector<DeviceRecipe> vecRecipeInfo = stRecipeInfo.vecDeviceList;
            pJobDataS->setMasterRecipe(nRecipeID);
            for (const auto& info : vecRecipeInfo) {
                const std::string& name = info.strDeviceName;
SourceCode/Bond/Servo/RecipeDeviceBindDlg.cpp
@@ -14,12 +14,12 @@
// ç»‘定界面需要显示的设备
static const std::vector<DeviceMetaInfo> g_vecBindDevices = {
    { EQ_ID_VACUUMBAKE,      EQ_NAME_VACUUMBAKE },
    { EQ_ID_Bonder1,         EQ_NAME_BONDER1 },
    { EQ_ID_Bonder2,         EQ_NAME_BONDER2 },
    { EQ_ID_BAKE_COOLING,    EQ_NAME_BAKE_COOLING },
    { EQ_ID_MEASUREMENT,     EQ_NAME_MEASUREMENT },
    { EQ_ID_EFEM,            EQ_NAME_EFEM }
    { EQ_ID_VACUUMBAKE,      EQ_NAME_VACUUMBAKE },
    { EQ_ID_Bonder1,         EQ_NAME_BONDER1 },
    { EQ_ID_Bonder2,         EQ_NAME_BONDER2 },
    { EQ_ID_BAKE_COOLING,    EQ_NAME_BAKE_COOLING },
    { EQ_ID_MEASUREMENT,     EQ_NAME_MEASUREMENT },
    { EQ_ID_EFEM,            EQ_NAME_EFEM }
};
// CRecipeDeviceBindDlg å¯¹è¯æ¡†
@@ -28,8 +28,8 @@
CRecipeDeviceBindDlg::CRecipeDeviceBindDlg(CWnd* pParent /*=nullptr*/)
    : CDialogEx(IDD_DIALOG_RECIPE_DEVICE_BIND, pParent)
    , m_strPPID(_T(""))
    , m_strDesc(_T(""))
    , m_strPPID(_T(""))
    , m_strDesc(_T(""))
{
}
@@ -39,116 +39,122 @@
}
const RecipeInfo& CRecipeDeviceBindDlg::GetRecipeInfo() const {
    return m_recipe;
    return m_recipe;
}
void CRecipeDeviceBindDlg::SetRecipeInfo(const RecipeInfo& info)
{
    m_recipe = info;
    m_recipe = info;
}
void CRecipeDeviceBindDlg::ReleaseDeviceControls()
{
    for (auto& ctrl : m_vecDevices) {
        delete ctrl.editDeviceID;    ctrl.editDeviceID = nullptr;
        delete ctrl.editDeviceName;  ctrl.editDeviceName = nullptr;
        delete ctrl.comboRecipeID;   ctrl.comboRecipeID = nullptr;
    }
    m_vecDevices.clear();
    for (auto& ctrl : m_vecDevices) {
        delete ctrl.editDeviceID;    ctrl.editDeviceID = nullptr;
        delete ctrl.editDeviceName;  ctrl.editDeviceName = nullptr;
        delete ctrl.comboRecipeID;   ctrl.comboRecipeID = nullptr;
    }
    m_vecDevices.clear();
}
void CRecipeDeviceBindDlg::CreateDeviceControls(int nXStart, int nYStart, int nTotalControlWidth, int nRowHeight)
{
    for (size_t i = 0; i < g_vecBindDevices.size(); ++i) {
        int y = nYStart + static_cast<int>(i) * nRowHeight;
        auto* pEditID = new CEdit;
        pEditID->Create(WS_CHILD | WS_VISIBLE | WS_BORDER | ES_CENTER, CRect(nXStart, y, nXStart + 100, y + 25), this, (UINT)(IDC_EDIT_DEVICEID_BASE + i));
        pEditID->SetFont(&m_font);
    for (size_t i = 0; i < g_vecBindDevices.size(); ++i) {
        int y = nYStart + static_cast<int>(i) * nRowHeight;
        auto* pEditID = new CEdit;
        pEditID->Create(WS_CHILD | WS_VISIBLE | WS_BORDER | ES_CENTER, CRect(nXStart, y, nXStart + 100, y + 25), this, (UINT)(IDC_EDIT_DEVICEID_BASE + i));
        pEditID->SetFont(&m_font);
        auto* pEditName = new CEdit;
        pEditName->Create(WS_CHILD | WS_VISIBLE | WS_BORDER | ES_CENTER, CRect(nXStart + 110, y, nXStart + 210, y + 25), this, (UINT)(IDC_EDIT_DEVICENAME_BASE + i));
        pEditName->SetFont(&m_font);
        auto* pEditName = new CEdit;
        pEditName->Create(WS_CHILD | WS_VISIBLE | WS_BORDER | ES_CENTER, CRect(nXStart + 110, y, nXStart + 210, y + 25), this, (UINT)(IDC_EDIT_DEVICENAME_BASE + i));
        pEditName->SetFont(&m_font);
        auto* pCombo = new CComboBox;
        pCombo->Create(WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST, CRect(nXStart + 220, y, nXStart + nTotalControlWidth, y + 25), this, (UINT)(IDC_COMBO_RECIPEID_BASE + i));
        pCombo->SetFont(&m_font);
        auto* pCombo = new CComboBox;
        pCombo->Create(WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST, CRect(nXStart + 220, y, nXStart + nTotalControlWidth, y + 25), this, (UINT)(IDC_COMBO_RECIPEID_BASE + i));
        pCombo->SetFont(&m_font);
        m_vecDevices.push_back({ pEditID, pEditName, pCombo });
    }
        m_vecDevices.push_back({ pEditID, pEditName, pCombo });
    }
}
bool CRecipeDeviceBindDlg::FillComboRecipeList(CComboBox* pCombo, int nDeviceID, int nSelectedRecipeID)
{
    auto& master = theApp.m_model.getMaster();
    auto* pEq = master.getEquipment(nDeviceID);
    if (!pEq) {
        return false;
    }
    auto& master = theApp.m_model.getMaster();
    auto* pEq = master.getEquipment(nDeviceID);
    if (!pEq) {
        return false;
    }
    auto* pRecipeList = pEq->getRecipeList(0);
    if (!pRecipeList) {
        return false;
    }
    auto* pRecipeList = pEq->getRecipeList(0);
    if (!pRecipeList) {
        return false;
    }
    auto& mapRecipeIds = pRecipeList->getIds();
    bool bFound = false;
    pCombo->ResetContent();
    for (const auto& pair : mapRecipeIds) {
        int nRecipeID = pair.second;
        CString strRecipeName;
        strRecipeName.Format(_T("%d"), nRecipeID);
        int idx = pCombo->AddString(strRecipeName);
        pCombo->SetItemData(idx, nRecipeID);
        if (nSelectedRecipeID == nRecipeID) {
            pCombo->SetCurSel(idx);
            bFound = true;
        }
    }
    auto& mapRecipeIds = pRecipeList->getIds();
    bool bFound = false;
    pCombo->ResetContent();
    for (const auto& pair : mapRecipeIds) {
        int nRecipeID = pair.second;
    if (nSelectedRecipeID != -1 && !bFound) {
        pCombo->SetCurSel(CB_ERR);
    }
    else if (pCombo->GetCount() > 0 && nSelectedRecipeID == -1) {
        pCombo->SetCurSel(0);
    }
        std::string strRecipeName = RecipeManager::getInstance().getDeviceRecipeName(pEq->getName(), nRecipeID);
        if (strRecipeName.empty()) {
            strRecipeName = std::to_string(nRecipeID);
        }
    return true;
        CString str;
        str.Format(_T("%s"), strRecipeName.c_str());
        int idx = pCombo->AddString(str);
        pCombo->SetItemData(idx, nRecipeID);
        if (nSelectedRecipeID == nRecipeID) {
            pCombo->SetCurSel(idx);
            bFound = true;
        }
    }
    if (nSelectedRecipeID != -1 && !bFound) {
        pCombo->SetCurSel(CB_ERR);
    }
    else if (pCombo->GetCount() > 0 && nSelectedRecipeID == -1) {
        pCombo->SetCurSel(0);
    }
    return true;
}
bool CRecipeDeviceBindDlg::FillDeviceInfo(int idx, int nDeviceID, const CString& strDeviceName, int nSelectedRecipeID)
{
    if (idx < 0 || idx >= (int)m_vecDevices.size()) {
        return false;
    }
    if (idx < 0 || idx >= (int)m_vecDevices.size()) {
        return false;
    }
    auto& ctrl = m_vecDevices[idx];
    CString strID;
    strID.Format(_T("%d"), nDeviceID);
    ctrl.editDeviceID->SetWindowText(strID);
    ctrl.editDeviceID->SetReadOnly(TRUE);
    ctrl.editDeviceName->SetWindowText(strDeviceName);
    ctrl.editDeviceName->SetReadOnly(TRUE);
    auto& ctrl = m_vecDevices[idx];
    CString strID;
    strID.Format(_T("%d"), nDeviceID);
    ctrl.editDeviceID->SetWindowText(strID);
    ctrl.editDeviceID->SetReadOnly(TRUE);
    ctrl.editDeviceName->SetWindowText(strDeviceName);
    ctrl.editDeviceName->SetReadOnly(TRUE);
    if (!FillComboRecipeList(ctrl.comboRecipeID, nDeviceID, nSelectedRecipeID)) {
        CString str;
        str.Format(_T("设备 [%s] æˆ–其配方列表未找到,请检查设备配置"), strDeviceName.GetString());
        AfxMessageBox(str);
        return false;
    }
    return true;
    if (!FillComboRecipeList(ctrl.comboRecipeID, nDeviceID, nSelectedRecipeID)) {
        CString str;
        str.Format(_T("设备 [%s] æˆ–其配方列表未找到,请检查设备配置"), strDeviceName.GetString());
        AfxMessageBox(str);
        return false;
    }
    return true;
}
void CRecipeDeviceBindDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Text(pDX, IDC_EDIT_PPID, m_strPPID);
    DDX_Text(pDX, IDC_EDIT_DESC, m_strDesc);
    CDialogEx::DoDataExchange(pDX);
    DDX_Text(pDX, IDC_EDIT_PPID, m_strPPID);
    DDX_Text(pDX, IDC_EDIT_DESC, m_strDesc);
}
BEGIN_MESSAGE_MAP(CRecipeDeviceBindDlg, CDialogEx)
    ON_WM_CLOSE()
    ON_WM_SIZE()
    ON_BN_CLICKED(IDOK, &CRecipeDeviceBindDlg::OnBnClickedOk)
    ON_WM_CLOSE()
    ON_WM_SIZE()
    ON_BN_CLICKED(IDOK, &CRecipeDeviceBindDlg::OnBnClickedOk)
END_MESSAGE_MAP()
@@ -159,62 +165,62 @@
    CDialogEx::OnInitDialog();
    // è®¾ç½®å¯¹è¯æ¡†æ ‡é¢˜
    SetWindowText(m_recipe.vecDeviceList.empty() ? _T("新建配方") : _T("编辑配方"));
    SetWindowText(m_recipe.vecDeviceList.empty() ? _T("新建配方") : _T("编辑配方"));
    // åˆ›å»ºåŠ¨æ€æŽ§ä»¶å­—ä½“
    if (!m_font.m_hObject) {
        CFont* pDlgFont = GetFont();
        LOGFONT lf;
        if (pDlgFont && pDlgFont->GetLogFont(&lf)) {
            lf.lfHeight = -16;
            m_font.CreateFontIndirect(&lf);
        }
    }
    if (!m_font.m_hObject) {
        CFont* pDlgFont = GetFont();
        LOGFONT lf;
        if (pDlgFont && pDlgFont->GetLogFont(&lf)) {
            lf.lfHeight = -16;
            m_font.CreateFontIndirect(&lf);
        }
    }
    // è®¡ç®—坐标
    CRect rDesc;
    int nXStart = 30, nYStart = 30, nTotalControlWidth = 340;
    if (auto* pWndDesc = GetDlgItem(IDC_STATIC_DESC)) {
        pWndDesc->GetWindowRect(&rDesc); ScreenToClient(&rDesc);
        nXStart = rDesc.left;
    }
    if (auto* pWndEdit = GetDlgItem(IDC_EDIT_DESC)) {
        pWndEdit->GetWindowRect(&rDesc); ScreenToClient(&rDesc);
        nYStart = rDesc.bottom + 20;
    }
    CRect rClient; GetClientRect(&rClient);
    nTotalControlWidth = rClient.Width() - nXStart * 2;
    const int nRowHeight = 30;
    // è®¡ç®—坐标
    CRect rDesc;
    int nXStart = 30, nYStart = 30, nTotalControlWidth = 340;
    if (auto* pWndDesc = GetDlgItem(IDC_STATIC_DESC)) {
        pWndDesc->GetWindowRect(&rDesc); ScreenToClient(&rDesc);
        nXStart = rDesc.left;
    }
    if (auto* pWndEdit = GetDlgItem(IDC_EDIT_DESC)) {
        pWndEdit->GetWindowRect(&rDesc); ScreenToClient(&rDesc);
        nYStart = rDesc.bottom + 20;
    }
    CRect rClient; GetClientRect(&rClient);
    nTotalControlWidth = rClient.Width() - nXStart * 2;
    const int nRowHeight = 30;
    // æ¸…空旧控件
    ReleaseDeviceControls();
    // æ¸…空旧控件
    ReleaseDeviceControls();
    // åˆ›å»ºæ–°æŽ§ä»¶
    CreateDeviceControls(nXStart, nYStart, nTotalControlWidth, nRowHeight);
    // åˆ›å»ºæ–°æŽ§ä»¶
    CreateDeviceControls(nXStart, nYStart, nTotalControlWidth, nRowHeight);
    auto& master = theApp.m_model.getMaster();
    auto& master = theApp.m_model.getMaster();
    // å¡«å……内容
    if (m_recipe.vecDeviceList.empty()) {
        // æ–°å»º
        for (size_t i = 0; i < g_vecBindDevices.size(); ++i) {
            const auto& meta = g_vecBindDevices[i];
            FillDeviceInfo((int)i, meta.nDeviceID, meta.strDeviceName);
        }
    }
    else {
        // ç¼–辑
        m_strPPID = CA2T(m_recipe.strPPID.c_str());
        m_strDesc = CA2T(m_recipe.strDescription.c_str());
        UpdateData(FALSE);
    // å¡«å……内容
    if (m_recipe.vecDeviceList.empty()) {
        // æ–°å»º
        for (size_t i = 0; i < g_vecBindDevices.size(); ++i) {
            const auto& meta = g_vecBindDevices[i];
            FillDeviceInfo((int)i, meta.nDeviceID, meta.strDeviceName);
        }
    }
    else {
        // ç¼–辑
        m_strPPID = CA2T(m_recipe.strPPID.c_str());
        m_strDesc = CA2T(m_recipe.strDescription.c_str());
        UpdateData(FALSE);
        for (size_t i = 0; i < m_recipe.vecDeviceList.size() && i < m_vecDevices.size(); ++i) {
            const auto& d = m_recipe.vecDeviceList[i];
            FillDeviceInfo((int)i, d.nDeviceID, d.strDeviceName.c_str(), d.nRecipeID);
        }
    }
        for (size_t i = 0; i < m_recipe.vecDeviceList.size() && i < m_vecDevices.size(); ++i) {
            const auto& d = m_recipe.vecDeviceList[i];
            FillDeviceInfo((int)i, d.nDeviceID, d.strDeviceName.c_str(), d.nRecipeID);
        }
    }
    CenterWindow();
    CenterWindow();
    return TRUE;  // return TRUE unless you set the focus to a control
    // å¼‚常: OCX å±žæ€§é¡µåº”返回 FALSE
@@ -222,53 +228,53 @@
void CRecipeDeviceBindDlg::OnClose()
{
    // TODO: åœ¨æ­¤æ·»åŠ æ¶ˆæ¯å¤„ç†ç¨‹åºä»£ç å’Œ/或调用默认值
    CDialogEx::OnClose();
    // TODO: åœ¨æ­¤æ·»åŠ æ¶ˆæ¯å¤„ç†ç¨‹åºä»£ç å’Œ/或调用默认值
    CDialogEx::OnClose();
    // æ¸…理控件
    ReleaseDeviceControls();
    // æ¸…理控件
    ReleaseDeviceControls();
}
void CRecipeDeviceBindDlg::OnSize(UINT nType, int cx, int cy)
{
    CDialogEx::OnSize(nType, cx, cy);
    CDialogEx::OnSize(nType, cx, cy);
    // TODO: åœ¨æ­¤å¤„添加消息处理程序代码
    // TODO: åœ¨æ­¤å¤„添加消息处理程序代码
}
void CRecipeDeviceBindDlg::OnBnClickedOk()
{
    // TODO: åœ¨æ­¤æ·»åŠ æŽ§ä»¶é€šçŸ¥å¤„ç†ç¨‹åºä»£ç 
    UpdateData(TRUE);
    // TODO: åœ¨æ­¤æ·»åŠ æŽ§ä»¶é€šçŸ¥å¤„ç†ç¨‹åºä»£ç 
    UpdateData(TRUE);
    // æ”¶é›†æ‰€æœ‰è®¾å¤‡æ˜ å°„
    m_recipe.vecDeviceList.clear();
    for (const auto& dev : m_vecDevices) {
        DeviceRecipe info;
        CString strID, strName;
        dev.editDeviceID->GetWindowText(strID);
        dev.editDeviceName->GetWindowText(strName);
    // æ”¶é›†æ‰€æœ‰è®¾å¤‡æ˜ å°„
    m_recipe.vecDeviceList.clear();
    for (const auto& dev : m_vecDevices) {
        DeviceRecipe info;
        CString strID, strName;
        dev.editDeviceID->GetWindowText(strID);
        dev.editDeviceName->GetWindowText(strName);
        int sel = dev.comboRecipeID->GetCurSel();
        info.nRecipeID = -1;
        if (sel != CB_ERR) {
            info.nRecipeID = (int)dev.comboRecipeID->GetItemData(sel);
        }
        info.nDeviceID = _ttoi(strID);
        info.strDeviceName = CT2A(strName);
        int sel = dev.comboRecipeID->GetCurSel();
        info.nRecipeID = -1;
        if (sel != CB_ERR) {
            info.nRecipeID = (int)dev.comboRecipeID->GetItemData(sel);
        }
        info.nDeviceID = _ttoi(strID);
        info.strDeviceName = CT2A(strName);
        m_recipe.vecDeviceList.push_back(info);
    }
        m_recipe.vecDeviceList.push_back(info);
    }
    // æ£€æŸ¥ PPID æ˜¯å¦ä¸ºç©º
    if (m_strPPID.IsEmpty()) {
        AfxMessageBox(_T("配方 PPID ä¸èƒ½ä¸ºç©º"));
        return;
    }
    // æ£€æŸ¥ PPID æ˜¯å¦ä¸ºç©º
    if (m_strPPID.IsEmpty()) {
        AfxMessageBox(_T("配方 PPID ä¸èƒ½ä¸ºç©º"));
        return;
    }
    // PPID和描述
    m_recipe.strPPID = CT2A(m_strPPID);
    m_recipe.strDescription = CT2A(m_strDesc);
    // PPID和描述
    m_recipe.strPPID = CT2A(m_strPPID);
    m_recipe.strDescription = CT2A(m_strDesc);
    CDialogEx::OnOK();
    CDialogEx::OnOK();
}
SourceCode/Bond/Servo/RecipeManager.cpp
@@ -1,4 +1,4 @@
#include "stdafx.h"
#include "stdafx.h"
#include "RecipeManager.h"
#include <sstream>
#include <iomanip>
@@ -8,408 +8,453 @@
std::recursive_mutex RecipeManager::m_mutex;
RecipeManager& RecipeManager::getInstance() {
    static RecipeManager instance;
    return instance;
    static RecipeManager instance;
    return instance;
}
RecipeManager::RecipeManager() {
    m_pDB = new BL::SQLiteDatabase();
    m_pDB = new BL::SQLiteDatabase();
}
RecipeManager::~RecipeManager() {
    if (m_pDB) {
        delete m_pDB;
        m_pDB = nullptr;
    }
    if (m_pDB) {
        delete m_pDB;
        m_pDB = nullptr;
    }
}
bool RecipeManager::initRecipeTable() {
    char szPath[MAX_PATH];
    GetModuleFileNameA(NULL, szPath, MAX_PATH);
    std::string exePath(szPath);
    std::string dbDir = exePath.substr(0, exePath.find_last_of("\\/")) + "\\DB";
    CreateDirectoryA(dbDir.c_str(), NULL);
    char szPath[MAX_PATH];
    GetModuleFileNameA(NULL, szPath, MAX_PATH);
    std::string exePath(szPath);
    std::string dbDir = exePath.substr(0, exePath.find_last_of("\\/")) + "\\DB";
    CreateDirectoryA(dbDir.c_str(), NULL);
    std::string dbPath = dbDir + "\\RecipeManager.db";
    if (!m_pDB->connect(dbPath, true)) {
        return false;
    }
    std::string dbPath = dbDir + "\\RecipeManager.db";
    if (!m_pDB->connect(dbPath, true)) {
        return false;
    }
    // å¯ç”¨ SQLite çš„外键约束支持
    if (!m_pDB->executeQuery("PRAGMA foreign_keys = ON;")) {
        std::cerr << "Failed to enable foreign keys." << std::endl;
        return false;
    }
    // å¯ç”¨ SQLite çš„外键约束支持
    if (!m_pDB->executeQuery("PRAGMA foreign_keys = ON;")) {
        std::cerr << "Failed to enable foreign keys." << std::endl;
        return false;
    }
    const std::string createRecipeTable = R"(
        CREATE TABLE IF NOT EXISTS recipes (
            ppid TEXT PRIMARY KEY NOT NULL,
            description TEXT,
            create_time TEXT DEFAULT (datetime('now', 'localtime'))
        );
    )";
    const std::string createRecipeTable = R"(
        CREATE TABLE IF NOT EXISTS recipes (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            ppid TEXT NOT NULL UNIQUE,
            description TEXT,
            create_time TEXT DEFAULT (datetime('now', 'localtime'))
        );
    )";
    const std::string createDeviceTable = R"(
        CREATE TABLE IF NOT EXISTS recipe_devices (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            ppid TEXT NOT NULL,
            device_id INTEGER NOT NULL,
            device_name TEXT NOT NULL,
            recipe_id INTEGER NOT NULL,
            FOREIGN KEY(ppid) REFERENCES recipes(ppid) ON DELETE CASCADE ON UPDATE CASCADE,
            UNIQUE (ppid, device_id),
            UNIQUE (ppid, device_name)
        );
    )";
    const std::string createDeviceTable = R"(
        CREATE TABLE IF NOT EXISTS recipe_devices (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            ppid TEXT NOT NULL,
            device_id INTEGER NOT NULL,
            device_name TEXT NOT NULL,
            recipe_id INTEGER NOT NULL,
            recipe_name TEXT NOT NULL,
            FOREIGN KEY(ppid) REFERENCES recipes(ppid) ON DELETE CASCADE ON UPDATE CASCADE,
            UNIQUE (ppid, device_id),
            UNIQUE (ppid, device_name)
        );
    )";
    return m_pDB->executeQuery(createRecipeTable) && m_pDB->executeQuery(createDeviceTable);
    return m_pDB->executeQuery(createRecipeTable) && m_pDB->executeQuery(createDeviceTable);
}
void RecipeManager::termRecipeTable() {
    if (!m_pDB) {
        return;
    }
    if (!m_pDB) {
        return;
    }
    m_pDB->disconnect();
    m_pDB->disconnect();
}
bool RecipeManager::destroyRecipeTable() {
    if (!m_pDB) {
    if (!m_pDB) {
        return false;
    }
    }
    return m_pDB->executeQuery("DROP TABLE IF EXISTS recipe_devices;") && m_pDB->executeQuery("DROP TABLE IF EXISTS recipes;");
    return m_pDB->executeQuery("DROP TABLE IF EXISTS recipe_devices;") && m_pDB->executeQuery("DROP TABLE IF EXISTS recipes;");
}
bool RecipeManager::ppidExists(const std::string& ppid) {
    std::ostringstream oss;
    oss << "SELECT COUNT(*) FROM recipes WHERE ppid = '" << ppid << "';";
    auto result = m_pDB->fetchResults(oss.str());
    return (!result.empty() && !result[0].empty() && result[0][0] != "0");
    std::ostringstream oss;
    oss << "SELECT COUNT(*) FROM recipes WHERE ppid = '" << ppid << "';";
    auto result = m_pDB->fetchResults(oss.str());
    return (!result.empty() && !result[0].empty() && result[0][0] != "0");
}
bool RecipeManager::deviceExists(const std::string& ppid, int nDeviceID) {
    std::ostringstream oss;
    oss << "SELECT COUNT(*) FROM recipe_devices WHERE ppid = '" << ppid
        << "' AND device_id = " << nDeviceID << ";";
    auto result = m_pDB->fetchResults(oss.str());
    return (!result.empty() && !result[0].empty() && result[0][0] != "0");
    std::ostringstream oss;
    oss << "SELECT COUNT(*) FROM recipe_devices WHERE ppid = '" << ppid
        << "' AND device_id = " << nDeviceID << ";";
    auto result = m_pDB->fetchResults(oss.str());
    return (!result.empty() && !result[0].empty() && result[0][0] != "0");
}
bool RecipeManager::addRecipe(const RecipeInfo& recipe) {
    if (!m_pDB || recipe.strPPID.empty() || recipe.vecDeviceList.empty()) {
        std::cerr << "[AddRecipe] Invalid input." << std::endl;
        return false;
    }
    if (!m_pDB || recipe.strPPID.empty() || recipe.vecDeviceList.empty()) {
        std::cerr << "[AddRecipe] Invalid input." << std::endl;
        return false;
    }
    std::string strTime = recipe.strCreateTime;
    if (strTime.empty()) {
        std::time_t now = std::time(nullptr);
        std::tm tm_now = {};
        localtime_s(&tm_now, &now);
        std::stringstream ss;
        ss << std::put_time(&tm_now, "%Y-%m-%d %H:%M:%S");
        strTime = ss.str();
    }
    std::string strTime = recipe.strCreateTime;
    if (strTime.empty()) {
        std::time_t now = std::time(nullptr);
        std::tm tm_now = {};
        localtime_s(&tm_now, &now);
        std::stringstream ss;
        ss << std::put_time(&tm_now, "%Y-%m-%d %H:%M:%S");
        strTime = ss.str();
    }
    std::lock_guard<std::recursive_mutex> lock(m_mutex);
    std::lock_guard<std::recursive_mutex> lock(m_mutex);
    // å¼€å§‹äº‹åŠ¡
    m_pDB->executeQuery("BEGIN TRANSACTION;");
    // å¼€å§‹äº‹åŠ¡
    m_pDB->executeQuery("BEGIN TRANSACTION;");
    std::ostringstream oss;
    oss << "INSERT OR REPLACE INTO recipes (ppid, description, create_time) VALUES ('"
        << recipe.strPPID << "', '"
        << recipe.strDescription << "', '"
        << strTime << "');";
    std::ostringstream oss;
    oss << "INSERT OR REPLACE INTO recipes (ppid, description, create_time) VALUES ('"
        << recipe.strPPID << "', '"
        << recipe.strDescription << "', '"
        << strTime << "');";
    if (!m_pDB->executeQuery(oss.str())) {
        std::cerr << "[AddRecipe] Failed to insert recipe: " << recipe.strPPID << std::endl;
        m_pDB->executeQuery("ROLLBACK;");
        return false;
    }
    if (!m_pDB->executeQuery(oss.str())) {
        std::cerr << "[AddRecipe] Failed to insert recipe: " << recipe.strPPID << std::endl;
        m_pDB->executeQuery("ROLLBACK;");
        return false;
    }
    for (const auto& device : recipe.vecDeviceList) {
        std::ostringstream devSql;
        devSql << "INSERT OR REPLACE INTO recipe_devices (ppid, device_id, device_name, recipe_id) VALUES ('"
            << recipe.strPPID << "', "
            << device.nDeviceID << ", '"
            << device.strDeviceName << "', "
            << device.nRecipeID << ");";
    for (const auto& device : recipe.vecDeviceList) {
        std::ostringstream devSql;
        devSql << "INSERT OR REPLACE INTO recipe_devices (ppid, device_id, device_name, recipe_id, recipe_name) VALUES ('"
            << recipe.strPPID << "', "
            << device.nDeviceID << ", '"
            << device.strDeviceName << "', "
            << device.nRecipeID << ", '"
            << device.strRecipeName << "');";
        if (!m_pDB->executeQuery(devSql.str())) {
            std::cerr << "[AddRecipe] Failed to insert device mapping: " << device.nDeviceID << std::endl;
            m_pDB->executeQuery("ROLLBACK;");
            return false;
        }
    }
    // æäº¤äº‹åŠ¡
    m_pDB->executeQuery("COMMIT;");
    return true;
        if (!m_pDB->executeQuery(devSql.str())) {
            std::cerr << "[AddRecipe] Failed to insert device mapping: " << device.nDeviceID << std::endl;
            m_pDB->executeQuery("ROLLBACK;");
            return false;
        }
    }
    // æäº¤äº‹åŠ¡
    m_pDB->executeQuery("COMMIT;");
    return true;
}
bool RecipeManager::addRecipeDevice(const std::string& ppid, const DeviceRecipe& device) {
    if (!m_pDB || ppid.empty() || device.nDeviceID <= 0 || device.nRecipeID <= 0) {
        std::cerr << "[addRecipeDevice] Invalid input." << std::endl;
        return false;
    }
    if (!m_pDB || ppid.empty() || device.nDeviceID <= 0 || device.nRecipeID <= 0) {
        std::cerr << "[addRecipeDevice] Invalid input." << std::endl;
        return false;
    }
    // æ£€æŸ¥ ppid æ˜¯å¦å­˜åœ¨
    // æ£€æŸ¥ ppid æ˜¯å¦å­˜åœ¨
    if (!ppidExists(ppid)) {
        std::cerr << "[addRecipeDevice] PPID does not exist: " << ppid << std::endl;
        return false;
    }
    // æ’入设备记录
    std::ostringstream oss;
    oss << "INSERT OR REPLACE INTO recipe_devices (ppid, device_id, device_name, recipe_id) VALUES ('"
        << ppid << "', "
        << device.nDeviceID << ", '"
        << device.strDeviceName << "', "
        << device.nRecipeID << ");";
    // æ’入设备记录
    std::ostringstream oss;
    oss << "INSERT OR REPLACE INTO recipe_devices (ppid, device_id, device_name, recipe_id, recipe_name) VALUES ('"
        << ppid << "', "
        << device.nDeviceID << ", '"
        << device.strDeviceName << "', "
        << device.nRecipeID << ", '"
        << device.strRecipeName << "');";
    std::lock_guard<std::recursive_mutex> lock(m_mutex);
    return m_pDB->executeQuery(oss.str());
    std::lock_guard<std::recursive_mutex> lock(m_mutex);
    return m_pDB->executeQuery(oss.str());
}
bool RecipeManager::deleteRecipeDeviceByID(const std::string& ppid, int nDeviceID) {
    if (!m_pDB || ppid.empty() || nDeviceID <= 0) {
        std::cerr << "[deleteRecipeDeviceByID] Invalid input." << std::endl;
        return false;
    }
    if (!m_pDB || ppid.empty() || nDeviceID <= 0) {
        std::cerr << "[deleteRecipeDeviceByID] Invalid input." << std::endl;
        return false;
    }
    std::ostringstream oss;
    oss << "DELETE FROM recipe_devices WHERE ppid = '" << ppid << "' AND device_id = " << nDeviceID << ";";
    std::ostringstream oss;
    oss << "DELETE FROM recipe_devices WHERE ppid = '" << ppid << "' AND device_id = " << nDeviceID << ";";
    std::lock_guard<std::recursive_mutex> lock(m_mutex);
    return m_pDB->executeQuery(oss.str());
    std::lock_guard<std::recursive_mutex> lock(m_mutex);
    return m_pDB->executeQuery(oss.str());
}
bool RecipeManager::deleteRecipeDeviceByName(const std::string& ppid, const std::string& strDeviceName) {
    if (!m_pDB || ppid.empty() || strDeviceName.empty()) {
        std::cerr << "[deleteRecipeDeviceByName] Invalid input." << std::endl;
        return false;
    }
    if (!m_pDB || ppid.empty() || strDeviceName.empty()) {
        std::cerr << "[deleteRecipeDeviceByName] Invalid input." << std::endl;
        return false;
    }
    std::ostringstream oss;
    oss << "DELETE FROM recipe_devices WHERE ppid = '" << ppid << "' AND device_name = '" << strDeviceName << "';";
    std::ostringstream oss;
    oss << "DELETE FROM recipe_devices WHERE ppid = '" << ppid << "' AND device_name = '" << strDeviceName << "';";
    std::lock_guard<std::recursive_mutex> lock(m_mutex);
    return m_pDB->executeQuery(oss.str());
    std::lock_guard<std::recursive_mutex> lock(m_mutex);
    return m_pDB->executeQuery(oss.str());
}
std::vector<RecipeInfo> RecipeManager::getAllRecipes() {
    if (!m_pDB) {
        return {};
    }
    if (!m_pDB) {
        return {};
    }
    std::vector<RecipeInfo> recipes;
    auto rows = m_pDB->fetchResults("SELECT ppid, description, create_time FROM recipes;");
    std::vector<RecipeInfo> recipes;
    auto rows = m_pDB->fetchResults("SELECT ppid, description, create_time FROM recipes;");
    for (const auto& row : rows) {
        RecipeInfo info;
        info.strPPID = row[0];
        info.strDescription = row[1];
        info.strCreateTime = row[2];
    for (const auto& row : rows) {
        RecipeInfo info;
        info.strPPID = row[0];
        info.strDescription = row[1];
        info.strCreateTime = row[2];
        std::ostringstream devQuery;
        devQuery << "SELECT device_id, device_name, recipe_id FROM recipe_devices WHERE ppid = '" << info.strPPID << "';";
        auto devs = m_pDB->fetchResults(devQuery.str());
        std::ostringstream devQuery;
        devQuery << "SELECT device_id, device_name, recipe_id, recipe_name FROM recipe_devices WHERE ppid = '" << info.strPPID << "'ORDER BY id ASC;";
        auto devs = m_pDB->fetchResults(devQuery.str());
        for (const auto& dev : devs) {
            DeviceRecipe dr;
            try {
                dr.nDeviceID = std::stoi(dev[0]);
                dr.strDeviceName = dev[1];
                dr.nRecipeID = std::stoi(dev[2]);
            }
            catch (...) {
                std::cerr << "Invalid data in recipe_devices for PPID: " << info.strPPID << std::endl;
                continue;
            }
            info.vecDeviceList.push_back(dr);
        }
        recipes.push_back(info);
    }
        for (const auto& dev : devs) {
            DeviceRecipe dr;
            try {
                dr.nDeviceID = std::stoi(dev[0]);
                dr.strDeviceName = dev[1];
                dr.nRecipeID = std::stoi(dev[2]);
                dr.strRecipeName = dev[3];
            }
            catch (...) {
                std::cerr << "Invalid data in recipe_devices for PPID: " << info.strPPID << std::endl;
                continue;
            }
            info.vecDeviceList.push_back(dr);
        }
        recipes.push_back(info);
    }
    return recipes;
    return recipes;
}
std::vector<RecipeInfo> RecipeManager::getRecipesByKeyword(const std::string& keyword) {
    std::vector<RecipeInfo> recipes;
    if (!m_pDB || keyword.empty()) {
        return recipes;
    }
    std::vector<RecipeInfo> recipes;
    if (!m_pDB || keyword.empty()) {
        return recipes;
    }
    std::ostringstream query;
    query << "SELECT ppid, description, create_time FROM recipes "
        << "WHERE ppid LIKE '%" << keyword << "%' OR description LIKE '%" << keyword << "%';";
    std::ostringstream query;
    query << "SELECT ppid, description, create_time FROM recipes "
        << "WHERE ppid LIKE '%" << keyword << "%' OR description LIKE '%" << keyword << "%';";
    auto rows = m_pDB->fetchResults(query.str());
    for (const auto& row : rows) {
        if (row.size() >= 3) {
            RecipeInfo info;
            info.strPPID = row[0];
            info.strDescription = row[1];
            info.strCreateTime = row[2];
            recipes.push_back(info);
        }
    }
    return recipes;
    auto rows = m_pDB->fetchResults(query.str());
    for (const auto& row : rows) {
        if (row.size() >= 3) {
            RecipeInfo info;
            info.strPPID = row[0];
            info.strDescription = row[1];
            info.strCreateTime = row[2];
            recipes.push_back(info);
        }
    }
    return recipes;
}
std::vector<std::string> RecipeManager::getAllPPID() const {
    std::vector<std::string> vecPPID;
    std::vector<std::string> vecPPID;
    if (!m_pDB) {
        return vecPPID;
    }
    if (!m_pDB) {
        return vecPPID;
    }
    const std::string query = "SELECT ppid FROM recipes ORDER BY ppid;";
    auto result = m_pDB->fetchResults(query);
    const std::string query = "SELECT ppid FROM recipes ORDER BY ppid;";
    auto result = m_pDB->fetchResults(query);
    for (const auto& row : result) {
        if (!row.empty()) {
            vecPPID.push_back(row[0]);
        }
    }
    for (const auto& row : result) {
        if (!row.empty()) {
            vecPPID.push_back(row[0]);
        }
    }
    return vecPPID;
    return vecPPID;
}
std::string RecipeManager::getPPIDById(int nId) {
    if (!m_pDB) {
        return {};
    }
    std::ostringstream query;
    query << "SELECT ppid FROM recipes WHERE id = " << nId << ";";
    auto rows = m_pDB->fetchResults(query.str());
    if (rows.empty() || rows[0].empty()) {
        return {};
    }
    return rows[0][0];
}
int RecipeManager::getIdByPPID(const std::string& ppid) {
    if (!m_pDB) {
        return -1;
    }
    std::ostringstream query;
    query << "SELECT id FROM recipes WHERE ppid = '" << ppid << "';";
    auto rows = m_pDB->fetchResults(query.str());
    if (rows.empty() || rows[0].empty()) {
        return -1;
    }
    try {
        return std::stoi(rows[0][0]);
    }
    catch (...) {
        std::cerr << "Invalid id value for PPID: " << ppid << std::endl;
        return -1;
    }
}
RecipeInfo RecipeManager::getRecipeByPPID(const std::string& ppid) {
    RecipeInfo info;
    auto rows = m_pDB->fetchResults("SELECT ppid, description, create_time FROM recipes WHERE ppid = '" + ppid + "';");
    if (rows.empty()) {
        return info;
    }
    RecipeInfo info;
    auto rows = m_pDB->fetchResults("SELECT ppid, description, create_time FROM recipes WHERE ppid = '" + ppid + "';");
    if (rows.empty()) {
        return info;
    }
    info.strPPID = rows[0][0];
    info.strDescription = rows[0][1];
    info.strCreateTime = rows[0][2];
    info.strPPID = rows[0][0];
    info.strDescription = rows[0][1];
    info.strCreateTime = rows[0][2];
    auto devs = m_pDB->fetchResults("SELECT device_id, device_name, recipe_id FROM recipe_devices WHERE ppid = '" + ppid + "';");
    for (const auto& dev : devs) {
        DeviceRecipe dr;
        try {
            dr.nDeviceID = std::stoi(dev[0]);
            dr.strDeviceName = dev[1];
            dr.nRecipeID = std::stoi(dev[2]);
        }
        catch (...) {
            std::cerr << "Invalid data in recipe_devices for PPID: " << ppid << std::endl;
            continue;
        }
        info.vecDeviceList.push_back(dr);
    }
    return info;
    auto devs = m_pDB->fetchResults("SELECT device_id, device_name, recipe_id, recipe_name FROM recipe_devices WHERE ppid = '" + ppid + "';");
    for (const auto& dev : devs) {
        DeviceRecipe dr;
        try {
            dr.nDeviceID = std::stoi(dev[0]);
            dr.strDeviceName = dev[1];
            dr.nRecipeID = std::stoi(dev[2]);
            dr.strRecipeName = dev[3];
        }
        catch (...) {
            std::cerr << "Invalid data in recipe_devices for PPID: " << ppid << std::endl;
            continue;
        }
        info.vecDeviceList.push_back(dr);
    }
    return info;
}
int RecipeManager::getDeviceRecipeIDByID(const std::string& ppid, int nDeviceID) {
    if (!m_pDB || ppid.empty() || nDeviceID <= 0) {
        return -1;
    }
    if (!m_pDB || ppid.empty() || nDeviceID <= 0) {
        return -1;
    }
    std::ostringstream query;
    query << "SELECT recipe_id FROM recipe_devices WHERE ppid = '" << ppid << "' AND device_id = " << nDeviceID << ";";
    std::ostringstream query;
    query << "SELECT recipe_id FROM recipe_devices WHERE ppid = '" << ppid << "' AND device_id = " << nDeviceID << ";";
    auto result = m_pDB->fetchResults(query.str());
    if (!result.empty() && !result[0].empty()) {
        try {
            return std::stoi(result[0][0]);
        }
        catch (...) {
            return -1;
        }
    }
    return -1;
    auto result = m_pDB->fetchResults(query.str());
    if (!result.empty() && !result[0].empty()) {
        try {
            return std::stoi(result[0][0]);
        }
        catch (...) {
            return -1;
        }
    }
    return -1;
}
int RecipeManager::getDeviceRecipeIDByName(const std::string& ppid, const std::string& strDeviceName) {
    if (!m_pDB || ppid.empty() || strDeviceName.empty()) {
        return -1;
    }
    if (!m_pDB || ppid.empty() || strDeviceName.empty()) {
        return -1;
    }
    std::ostringstream query;
    query << "SELECT recipe_id FROM recipe_devices WHERE ppid = '" << ppid << "' AND device_name = '" << strDeviceName << "';";
    std::ostringstream query;
    query << "SELECT recipe_id FROM recipe_devices WHERE ppid = '" << ppid << "' AND device_name = '" << strDeviceName << "';";
    auto result = m_pDB->fetchResults(query.str());
    if (!result.empty() && !result[0].empty()) {
        try {
            return std::stoi(result[0][0]);
        }
        catch (...) {
            return -1;
        }
    }
    return -1;
    auto result = m_pDB->fetchResults(query.str());
    if (!result.empty() && !result[0].empty()) {
        try {
            return std::stoi(result[0][0]);
        }
        catch (...) {
            return -1;
        }
    }
    return -1;
}
bool RecipeManager::deleteRecipeByPPID(const std::string& ppid) {
    if (!m_pDB) {
        return false;
    }
    if (!m_pDB) {
        return false;
    }
    std::lock_guard<std::recursive_mutex> lock(m_mutex);
    return m_pDB->executeQuery("DELETE FROM recipes WHERE ppid = '" + ppid + "';");
    std::lock_guard<std::recursive_mutex> lock(m_mutex);
    return m_pDB->executeQuery("DELETE FROM recipes WHERE ppid = '" + ppid + "';");
}
bool RecipeManager::updateRecipe(const RecipeInfo& recipe) {
    if (!m_pDB) {
        return false;
    }
    if (!m_pDB) {
        return false;
    }
    if (recipe.strPPID.empty()) {
        std::cerr << "Recipe PPID cannot be empty." << std::endl;
        return false;
    }
    std::lock_guard<std::recursive_mutex> lock(m_mutex);
    deleteRecipeByPPID(recipe.strPPID);
    return addRecipe(recipe);
    std::lock_guard<std::recursive_mutex> lock(m_mutex);
    deleteRecipeByPPID(recipe.strPPID);
    return addRecipe(recipe);
}
bool RecipeManager::updatePPID(const std::string& oldPPID, const std::string& newPPID) {
    if (!m_pDB || oldPPID.empty() || newPPID.empty()) {
        std::cerr << "[updatePPID] Invalid input." << std::endl;
        return false;
    }
    if (!m_pDB || oldPPID.empty() || newPPID.empty()) {
        std::cerr << "[updatePPID] Invalid input." << std::endl;
        return false;
    }
    std::lock_guard<std::recursive_mutex> lock(m_mutex);
    std::lock_guard<std::recursive_mutex> lock(m_mutex);
    // æ£€æŸ¥æ˜¯å¦å·²ç»å­˜åœ¨ç›¸åŒçš„ newPPID
    auto check = m_pDB->fetchResults("SELECT COUNT(*) FROM recipes WHERE ppid = '" + newPPID + "';");
    if (!check.empty() && !check[0].empty() && check[0][0] != "0") {
        std::cerr << "[updatePPID] New PPID already exists: " << newPPID << std::endl;
        return false;
    }
    // æ£€æŸ¥æ˜¯å¦å·²ç»å­˜åœ¨ç›¸åŒçš„ newPPID
    auto check = m_pDB->fetchResults("SELECT COUNT(*) FROM recipes WHERE ppid = '" + newPPID + "';");
    if (!check.empty() && !check[0].empty() && check[0][0] != "0") {
        std::cerr << "[updatePPID] New PPID already exists: " << newPPID << std::endl;
        return false;
    }
    m_pDB->executeQuery("BEGIN TRANSACTION;");
    m_pDB->executeQuery("BEGIN TRANSACTION;");
    std::ostringstream sql;
    sql << "UPDATE recipes SET ppid = '" << newPPID << "' WHERE ppid = '" << oldPPID << "';";
    if (!m_pDB->executeQuery(sql.str())) {
        std::cerr << "[updatePPID] Failed to update recipes table." << std::endl;
        m_pDB->executeQuery("ROLLBACK;");
        return false;
    }
    std::ostringstream sql;
    sql << "UPDATE recipes SET ppid = '" << newPPID << "' WHERE ppid = '" << oldPPID << "';";
    if (!m_pDB->executeQuery(sql.str())) {
        std::cerr << "[updatePPID] Failed to update recipes table." << std::endl;
        m_pDB->executeQuery("ROLLBACK;");
        return false;
    }
    m_pDB->executeQuery("COMMIT;");
    return true;
    m_pDB->executeQuery("COMMIT;");
    return true;
}
bool RecipeManager::updateDescription(const std::string& ppid, const std::string& newDescription) {
    if (!m_pDB || ppid.empty()) {
        std::cerr << "[updateRecipeDescription] Invalid input." << std::endl;
        return false;
    }
    if (!m_pDB || ppid.empty()) {
        std::cerr << "[updateRecipeDescription] Invalid input." << std::endl;
        return false;
    }
    std::ostringstream oss;
    oss << "UPDATE recipes SET description = '" << newDescription << "' WHERE ppid = '" << ppid << "';";
    std::ostringstream oss;
    oss << "UPDATE recipes SET description = '" << newDescription << "' WHERE ppid = '" << ppid << "';";
    std::lock_guard<std::recursive_mutex> lock(m_mutex);
    return m_pDB->executeQuery(oss.str());
    std::lock_guard<std::recursive_mutex> lock(m_mutex);
    return m_pDB->executeQuery(oss.str());
}
bool RecipeManager::updateDeviceRecipeIDByID(const std::string& ppid, int nDeviceID, int nNewRecipeID) {
@@ -418,7 +463,7 @@
    }
    std::ostringstream query;
    std::lock_guard<std::recursive_mutex> lock(m_mutex);
    std::lock_guard<std::recursive_mutex> lock(m_mutex);
    query << "UPDATE recipe_devices SET recipe_id = " << nNewRecipeID
        << " WHERE ppid = '" << ppid << "' AND device_id = " << nDeviceID << ";";
    return m_pDB->executeQuery(query.str());
@@ -429,98 +474,222 @@
        return false;
    }
    std::ostringstream query;
    std::lock_guard<std::recursive_mutex> lock(m_mutex);
    std::lock_guard<std::recursive_mutex> lock(m_mutex);
    query << "UPDATE recipe_devices SET recipe_id = " << nNewRecipeID
        << " WHERE ppid = '" << ppid << "' AND device_name = '" << strDeviceName << "';";
    return m_pDB->executeQuery(query.str());
}
bool RecipeManager::addDeviceRecipe(const std::string& strDeviceName, int nID, const std::string& strName, const std::string& strPara) {
    if (!m_pDB || strDeviceName.empty() || nID <= 0 || strName.empty() || strPara.empty()) {
        return false;
    }
    std::ostringstream sql;
    sql << "CREATE TABLE IF NOT EXISTS " << strDeviceName << "_Recipes ("
        << "recipe_id INTEGER PRIMARY KEY,"
        << "recipe_name TEXT NOT NULL,"
        << "recipe_para TEXT NOT NULL"
        << ");";
    m_pDB->executeQuery(sql.str());
    std::ostringstream ins;
    ins << "INSERT OR REPLACE INTO " << strDeviceName
        << "_Recipes (recipe_id, recipe_name, recipe_para) VALUES ("
        << nID << ", '" << strName << "', '" << strPara << "');";
    std::lock_guard<std::recursive_mutex> lk(m_mutex);
    return m_pDB->executeQuery(ins.str());
}
bool RecipeManager::updateDeviceRecipeName(const std::string& strDeviceName, int nID, const std::string& strNewName) {
    if (!m_pDB || strDeviceName.empty() || nID <= 0 || strNewName.empty()) {
        return false;
    }
    std::ostringstream sql;
    sql << "UPDATE " << strDeviceName << "_Recipes SET recipe_name='" << strNewName
        << "' WHERE recipe_id=" << nID << ";";
    std::lock_guard<std::recursive_mutex> lk(m_mutex);
    return m_pDB->executeQuery(sql.str());
}
bool RecipeManager::updateDeviceRecipePara(const std::string& strDeviceName, int nID, const std::string& strNewPara) {
    if (!m_pDB || strDeviceName.empty() || nID <= 0) {
        return false;
    }
    std::ostringstream sql;
    sql << "UPDATE " << strDeviceName << "_Recipes SET recipe_para='" << strNewPara
        << "' WHERE recipe_id=" << nID << ";";
    std::lock_guard<std::recursive_mutex> lk(m_mutex);
    return m_pDB->executeQuery(sql.str());
}
std::string RecipeManager::getDeviceRecipeName(const std::string& strDeviceName, int nID) {
    if (!m_pDB || strDeviceName.empty() || nID <= 0) {
        return "";
    }
    std::ostringstream sql;
    sql << "SELECT recipe_name FROM " << strDeviceName << "_Recipes "
        << "WHERE recipe_id=" << nID << " LIMIT 1;";
    auto rows = m_pDB->fetchResults(sql.str());
    if (!rows.empty() && !rows[0].empty()) {
        return rows[0][0];
    }
    return "";
}
std::string RecipeManager::getDeviceRecipePara(const std::string& strDeviceName, int nID) {
    if (!m_pDB || strDeviceName.empty() || nID <= 0) {
        return "";
    }
    std::ostringstream sql;
    sql << "SELECT recipe_para FROM " << strDeviceName << "_Recipes "
        << "WHERE recipe_id=" << nID << " LIMIT 1;";
    auto rows = m_pDB->fetchResults(sql.str());
    if (!rows.empty() && !rows[0].empty()) {
        return rows[0][0];
    }
    return "";
}
bool RecipeManager::deleteDeviceRecipe(const std::string& strDeviceName, int nID) {
    if (!m_pDB || strDeviceName.empty() || nID <= 0) {
        return false;
    }
    std::ostringstream sql;
    sql << "DELETE FROM " << strDeviceName << "_Recipes WHERE recipe_id=" << nID << ";";
    std::lock_guard<std::recursive_mutex> lk(m_mutex);
    return m_pDB->executeQuery(sql.str());
}
std::vector<std::pair<int, std::string>> RecipeManager::getDeviceRecipes(const std::string& strDeviceName) {
    std::vector<std::pair<int, std::string>> out;
    if (!m_pDB || strDeviceName.empty()) {
        return out;
    }
    std::ostringstream sql;
    sql << "SELECT recipe_id, recipe_name FROM " << strDeviceName << "_Recipes ORDER BY recipe_id;";
    auto rows = m_pDB->fetchResults(sql.str());
    for (const auto& r : rows) {
        if (r.size() < 2) continue;
        try {
            int id = std::stoi(r[0]);
            out.emplace_back(id, r[1]);
        }
        catch (...) {}
    }
    return out;
}
void RecipeManager::insertMockData() {
    if (!m_pDB) {
        return;
    }
    if (!m_pDB) {
        return;
    }
    RecipeInfo recipe;
    recipe.strPPID = "P1001";
    recipe.strDescription = "Main Board Burn-in";
    RecipeInfo recipe;
    recipe.strPPID = "P1001";
    recipe.strDescription = "Main Board Burn-in";
    recipe.vecDeviceList = {
        {1, 101, "Burner A"},
        {2, 102, "Burner B"}
    };
    recipe.vecDeviceList = {
        {9, 101, "VacuumBake", "VacuumBake"},
        {10, 102, "Bonder1", "Bonder1"},
        {11, 103, "Bonder2", "Bonder2"}
    };
    addRecipe(recipe);
    addRecipe(recipe);
    addDeviceRecipe("Bonder1", 101, "标准工艺", "");
    addDeviceRecipe("Bonder1", 102, "改良工艺", "");
    addDeviceRecipe("Bonder1", 103, "高速模式", "");
    addDeviceRecipe("Bonder2", 101, "标准工艺", "");
    addDeviceRecipe("Bonder2", 102, "改良工艺", "");
    addDeviceRecipe("Bonder2", 103, "高速模式", "");
}
bool RecipeManager::readRecipeFile(const std::string& filename) {
    if (!m_pDB) {
        return false;
    }
    if (!m_pDB) {
        return false;
    }
    std::ifstream file(filename);
    if (!file.is_open()) {
        return false;
    }
    std::ifstream file(filename);
    if (!file.is_open()) {
        return false;
    }
    std::unordered_map<std::string, RecipeInfo> recipeMap;
    std::string line;
    std::getline(file, line); // skip header
    std::unordered_map<std::string, RecipeInfo> recipeMap;
    std::string line;
    std::getline(file, line); // skip header
    while (std::getline(file, line)) {
        std::stringstream ss(line);
        std::string cell;
        std::string ppid, description, createTime;
        DeviceRecipe dev;
    while (std::getline(file, line)) {
        std::stringstream ss(line);
        std::string cell;
        std::string ppid, description, createTime;
        DeviceRecipe dev;
        std::getline(ss, ppid, ',');
        std::getline(ss, cell, ',');
        try { dev.nDeviceID = std::stoi(cell); }
        catch (...) { continue; }
        std::getline(ss, dev.strDeviceName, ',');
        std::getline(ss, cell, ',');
        try { dev.nRecipeID = std::stoi(cell); }
        catch (...) { continue; }
        std::getline(ss, description, ',');
        std::getline(ss, createTime, ',');
        std::getline(ss, ppid, ',');
        std::getline(ss, cell, ',');
        try { dev.nDeviceID = std::stoi(cell); }
        catch (...) { continue; }
        std::getline(ss, dev.strDeviceName, ',');
        std::getline(ss, cell, ',');
        try { dev.nRecipeID = std::stoi(cell); }
        catch (...) { continue; }
        std::getline(ss, dev.strRecipeName, ',');
        std::getline(ss, description, ',');
        std::getline(ss, createTime, ',');
        auto& recipe = recipeMap[ppid];
        recipe.strPPID = ppid;
        recipe.strDescription = description;
        recipe.strCreateTime = createTime;
        recipe.vecDeviceList.push_back(dev);
    }
        auto& recipe = recipeMap[ppid];
        recipe.strPPID = ppid;
        recipe.strDescription = description;
        recipe.strCreateTime = createTime;
        recipe.vecDeviceList.push_back(dev);
    }
    for (const auto& pair : recipeMap) {
        if (!updateRecipe(pair.second)) {
            std::cerr << "Failed to update recipe from file: " << pair.first << std::endl;
        }
    }
    for (const auto& pair : recipeMap) {
        if (!updateRecipe(pair.second)) {
            std::cerr << "Failed to update recipe from file: " << pair.first << std::endl;
        }
    }
    return true;
    return true;
}
bool RecipeManager::saveRecipeFile(const std::string& filename) {
    if (!m_pDB) {
        return false;
    }
    if (!m_pDB) {
        return false;
    }
    std::ofstream file(filename);
    if (!file.is_open()) {
        return false;
    }
    std::ofstream file(filename);
    if (!file.is_open()) {
        return false;
    }
    file << "PPID,DeviceID,DeviceName,RecipeID,Description,CreateTime\n";
    auto recipes = getAllRecipes();
    for (const auto& recipe : recipes) {
        for (const auto& dev : recipe.vecDeviceList) {
            file << recipe.strPPID << ","
                << dev.nDeviceID << ","
                << dev.strDeviceName << ","
                << dev.nRecipeID << ","
                << recipe.strDescription << ","
                << recipe.strCreateTime << "\n";
        }
    }
    file << "PPID,DeviceID,DeviceName,RecipeID,RecipeName,Description,CreateTime\n";
    auto recipes = getAllRecipes();
    for (const auto& recipe : recipes) {
        for (const auto& dev : recipe.vecDeviceList) {
            file << recipe.strPPID << ","
                << dev.nDeviceID << ","
                << dev.strDeviceName << ","
                << dev.nRecipeID << ","
                << dev.strRecipeName << ","
                << recipe.strDescription << ","
                << recipe.strCreateTime << "\n";
        }
    }
    return true;
    return true;
}
SourceCode/Bond/Servo/RecipeManager.h
@@ -1,4 +1,4 @@
#ifndef RECIPE_MANAGER_H
#ifndef RECIPE_MANAGER_H
#define RECIPE_MANAGER_H
#include <string>
@@ -6,110 +6,128 @@
#include <mutex>
#include <unordered_map>
#include "Database.h"
#include "CParam.h"
// å•个设备配方映射信息
// å•个设备配方映射信息
struct DeviceRecipe {
    int nDeviceID;               // è®¾å¤‡ID
    int nRecipeID;               // å­é…æ–¹ID
    std::string strRecipeName;   // å­é…æ–¹åç§°
    std::string strDeviceName;   // è®¾å¤‡åç§°
    int nDeviceID;                            // è®¾å¤‡ID
    int nRecipeID;                            // å­é…æ–¹ID
    std::string strRecipeName;                // è®¾å¤‡åç§°
    std::string strDeviceName;                // å­é…æ–¹åç§°
    std::vector<uint8_t> paramsRawData;     // åŽŸå§‹å‚æ•°æ•°æ®
    std::vector<CParam*> m_params;            // å‚数对象列表
};
// é…æ–¹ä¿¡æ¯
// é…æ–¹ä¿¡æ¯
struct RecipeInfo {
    std::string strPPID;         // é…æ–¹ID
    std::string strDescription;  // é…æ–¹æè¿°
    std::string strCreateTime;   // åˆ›å»ºæ—¶é—´
    std::vector<DeviceRecipe> vecDeviceList;  // å…³è”的设备信息列表
    std::string strPPID;         // é…æ–¹ID
    std::string strDescription;  // é…æ–¹æè¿°
    std::string strCreateTime;   // åˆ›å»ºæ—¶é—´
    std::vector<DeviceRecipe> vecDeviceList;  // å…³è”的设备信息列表
};
using RecipeMap = std::unordered_map<std::string, RecipeInfo>; // æŒ‰ PPID æ˜ å°„的配方表
using RecipeMap = std::unordered_map<std::string, RecipeInfo>; // æŒ‰ PPID æ˜ å°„的配方表
class RecipeManager {
public:
    // èŽ·å–å•ä¾‹
    static RecipeManager& getInstance();
    // èŽ·å–å•ä¾‹
    static RecipeManager& getInstance();
    // åˆå§‹åŒ–配方数据库
    bool initRecipeTable();
    // åˆå§‹åŒ–配方数据库
    bool initRecipeTable();
    // é”€æ¯è¡¨æˆ–关闭连接
    void termRecipeTable();
    bool destroyRecipeTable();
    // é”€æ¯è¡¨æˆ–关闭连接
    void termRecipeTable();
    bool destroyRecipeTable();
    // æ£€æŸ¥ PPID æ˜¯å¦å­˜åœ¨
    bool ppidExists(const std::string& ppid);
    // æ£€æŸ¥ PPID æ˜¯å¦å­˜åœ¨
    bool ppidExists(const std::string& ppid);
    // æ£€æŸ¥è®¾å¤‡æ˜¯å¦å­˜åœ¨äºŽæŒ‡å®š PPID çš„配方中
    // æ£€æŸ¥è®¾å¤‡æ˜¯å¦å­˜åœ¨äºŽæŒ‡å®š PPID çš„配方中
    bool deviceExists(const std::string& ppid, int nDeviceID);
    // æ·»åŠ ä¸€ä¸ªé…æ–¹åŠå…¶è®¾å¤‡æ˜ å°„
    bool addRecipe(const RecipeInfo& recipe);
    // æ·»åŠ ä¸€ä¸ªé…æ–¹åŠå…¶è®¾å¤‡æ˜ å°„
    bool addRecipe(const RecipeInfo& recipe);
    // æ·»åŠ è®¾å¤‡åˆ°æŒ‡å®šé…æ–¹
    bool addRecipeDevice(const std::string& ppid, const DeviceRecipe& device);
    // æ·»åŠ è®¾å¤‡åˆ°æŒ‡å®šé…æ–¹
    bool addRecipeDevice(const std::string& ppid, const DeviceRecipe& device);
    // åˆ é™¤æŒ‡å®š PPID çš„设备配方
    bool deleteRecipeDeviceByID(const std::string& ppid, int nDeviceID);
    // åˆ é™¤æŒ‡å®š PPID çš„设备配方
    bool deleteRecipeDeviceByID(const std::string& ppid, int nDeviceID);
    // åˆ é™¤æŒ‡å®š PPID çš„设备配方(通过设备名称)
    bool deleteRecipeDeviceByName(const std::string& ppid, const std::string& strDeviceName);
    // åˆ é™¤æŒ‡å®š PPID çš„设备配方(通过设备名称)
    bool deleteRecipeDeviceByName(const std::string& ppid, const std::string& strDeviceName);
    // æŸ¥è¯¢æ‰€æœ‰é…æ–¹
    std::vector<RecipeInfo> getAllRecipes();
    // æŸ¥è¯¢æ‰€æœ‰é…æ–¹
    std::vector<RecipeInfo> getAllRecipes();
    // æ ¹æ® PPID æˆ–描述查询配方
    std::vector<RecipeInfo> getRecipesByKeyword(const std::string& keyword);
    // æ ¹æ® PPID æˆ–描述查询配方
    std::vector<RecipeInfo> getRecipesByKeyword(const std::string& keyword);
    // èŽ·å–æ‰€æœ‰ PPID
    std::vector<std::string> getAllPPID() const;
    // èŽ·å–æ‰€æœ‰ PPID
    std::vector<std::string> getAllPPID() const;
    // æŒ‰ PPID æŸ¥è¯¢é…æ–¹
    RecipeInfo getRecipeByPPID(const std::string& ppid);
    // æŒ‰ ID æŸ¥è¯¢ PPID
    std::string getPPIDById(int nId);
    // æ ¹æ® PPID å’Œè®¾å¤‡ID èŽ·å–è®¾å¤‡é…æ–¹ID
    int getDeviceRecipeIDByID(const std::string& ppid, int nDeviceID);
    // æŒ‰ PPID æŸ¥è¯¢ ID
    int getIdByPPID(const std::string& ppid);
    // æ ¹æ® PPID å’Œè®¾å¤‡åç§° èŽ·å–è®¾å¤‡é…æ–¹ID
    int getDeviceRecipeIDByName(const std::string& ppid, const std::string& strDeviceName);
    // æŒ‰ PPID æŸ¥è¯¢é…æ–¹
    RecipeInfo getRecipeByPPID(const std::string& ppid);
    // åˆ é™¤æŒ‡å®š PPID çš„配方
    bool deleteRecipeByPPID(const std::string& ppid);
    // æ ¹æ® PPID å’Œè®¾å¤‡ID èŽ·å–è®¾å¤‡é…æ–¹ID
    int getDeviceRecipeIDByID(const std::string& ppid, int nDeviceID);
    // æ›´æ–°æŒ‡å®š PPID çš„配方
    bool updateRecipe(const RecipeInfo& recipe);
    // æ ¹æ® PPID å’Œè®¾å¤‡åç§° èŽ·å–è®¾å¤‡é…æ–¹ID
    int getDeviceRecipeIDByName(const std::string& ppid, const std::string& strDeviceName);
    // æ›´æ–° PPID(通过旧 PPID å’Œæ–° PPID)
    bool updatePPID(const std::string& oldPPID, const std::string& newPPID);
    // åˆ é™¤æŒ‡å®š PPID çš„配方
    bool deleteRecipeByPPID(const std::string& ppid);
    // æ›´æ–°é…æ–¹æè¿°ï¼ˆé€šè¿‡ PPID)
    bool updateDescription(const std::string& ppid, const std::string& newDescription);
    // æ›´æ–°æŒ‡å®š PPID çš„配方
    bool updateRecipe(const RecipeInfo& recipe);
    // æ›´æ–°è®¾å¤‡é…æ–¹ID(通过 PPID å’Œè®¾å¤‡ID)
    bool updateDeviceRecipeIDByID(const std::string& ppid, int nDeviceID, int nNewRecipeID);
    // æ›´æ–° PPID(通过旧 PPID å’Œæ–° PPID)
    bool updatePPID(const std::string& oldPPID, const std::string& newPPID);
    // æ›´æ–°è®¾å¤‡é…æ–¹ID(通过 PPID å’Œè®¾å¤‡åç§°ï¼‰
    bool updateDeviceRecipeIDByName(const std::string& ppid, const std::string& strDeviceName, int nNewRecipeID);
    // æ›´æ–°é…æ–¹æè¿°ï¼ˆé€šè¿‡ PPID)
    bool updateDescription(const std::string& ppid, const std::string& newDescription);
    // æ¨¡æ‹Ÿæ’入数据(测试用)
    void insertMockData();
    // æ›´æ–°è®¾å¤‡é…æ–¹ID(通过 PPID å’Œè®¾å¤‡ID)
    bool updateDeviceRecipeIDByID(const std::string& ppid, int nDeviceID, int nNewRecipeID);
    // è¯»å–配方文件(CSV æˆ– JSON)
    bool readRecipeFile(const std::string& filename);
    // æ›´æ–°è®¾å¤‡é…æ–¹ID(通过 PPID å’Œè®¾å¤‡åç§°ï¼‰
    bool updateDeviceRecipeIDByName(const std::string& ppid, const std::string& strDeviceName, int nNewRecipeID);
    // ä¿å­˜é…æ–¹åˆ°æ–‡ä»¶
    bool saveRecipeFile(const std::string& filename);
    bool addDeviceRecipe(const std::string& strDeviceName, int nID, const std::string& strName, const std::string& strPara);
    bool updateDeviceRecipeName(const std::string& strDeviceName, int nID, const std::string& strNewName);
    bool updateDeviceRecipePara(const std::string& strDeviceName, int nID, const std::string& strNewPara);
    std::string getDeviceRecipeName(const std::string& strDeviceName, int nID);
    std::string getDeviceRecipePara(const std::string& strDeviceName, int nID);
    bool deleteDeviceRecipe(const std::string& strDeviceName, int nID);
    std::vector<std::pair<int, std::string>> getDeviceRecipes(const std::string& strDeviceName);
    // æ¨¡æ‹Ÿæ’入数据(测试用)
    void insertMockData();
    // è¯»å–配方文件(CSV æˆ– JSON)
    bool readRecipeFile(const std::string& filename);
    // ä¿å­˜é…æ–¹åˆ°æ–‡ä»¶
    bool saveRecipeFile(const std::string& filename);
private:
    RecipeManager();
    ~RecipeManager();
    RecipeManager();
    ~RecipeManager();
    RecipeManager(const RecipeManager&) = delete;
    RecipeManager& operator=(const RecipeManager&) = delete;
    RecipeManager(const RecipeManager&) = delete;
    RecipeManager& operator=(const RecipeManager&) = delete;
private:
    BL::Database* m_pDB;
    static std::recursive_mutex m_mutex;
    BL::Database* m_pDB;
    static std::recursive_mutex m_mutex;
};
#endif // RECIPE_MANAGER_H
SourceCode/Bond/Servo/Servo.vcxproj
@@ -214,6 +214,7 @@
    <ClInclude Include="CPageLinkSignal.h" />
    <ClInclude Include="CPageReport.h" />
    <ClInclude Include="CPageVarialbles.h" />
    <ClInclude Include="CParam.h" />
    <ClInclude Include="CReport.h" />
    <ClInclude Include="CRobotCmdContainerDlg.h" />
    <ClInclude Include="CRobotCmdTestDlg.h" />
@@ -368,6 +369,7 @@
    <ClCompile Include="CPageLinkSignal.cpp" />
    <ClCompile Include="CPageReport.cpp" />
    <ClCompile Include="CPageVarialbles.cpp" />
    <ClCompile Include="CParam.cpp" />
    <ClCompile Include="CReport.cpp" />
    <ClCompile Include="CRobotCmdContainerDlg.cpp" />
    <ClCompile Include="CRobotCmdTestDlg.cpp" />
SourceCode/Bond/Servo/Servo.vcxproj.filters
@@ -180,6 +180,7 @@
    <ClCompile Include="CControlJob.cpp" />
    <ClCompile Include="CExpandableListCtrl.cpp" />
    <ClCompile Include="CControlJobDlg.cpp" />
    <ClCompile Include="CParam.cpp" />
  </ItemGroup>
  <ItemGroup>
    <ClInclude Include="AlarmManager.h" />
@@ -366,6 +367,7 @@
    <ClInclude Include="SerializeUtil.h" />
    <ClInclude Include="CExpandableListCtrl.h" />
    <ClInclude Include="CControlJobDlg.h" />
    <ClInclude Include="CParam.h" />
  </ItemGroup>
  <ItemGroup>
    <ResourceCompile Include="Servo.rc" />
SourceCode/Bond/Servo/ServoDlg.cpp
@@ -27,6 +27,7 @@
#include "CPageReport.h"
#include "CPageCollectionEvent.h"
#include "CControlJobDlg.h"
#include "InputDialog.h"
#ifdef _DEBUG
@@ -956,6 +957,28 @@
LRESULT CServoDlg::OnToolbarBtnClicked(WPARAM wParam, LPARAM lParam)
{
    int id = (int)lParam;
    if (id == IDC_BUTTON_RUN || id == IDC_BUTTON_STOP) {
        //CInputDialog inputDialog(_T("验证用户"), _T("请输入用户密码:"));
        //if (inputDialog.DoModal() != IDOK) {
        //    AfxMessageBox(_T("取消验证!"));
        //    return 0;
        //}
        //CString inputText = inputDialog.GetInputText();
        //std::string strPass = UserManager::getInstance().getCurrentPass();
        //if (inputText.Compare(strPass.c_str()) != 0) {
        //    AfxMessageBox(_T("密码错误!"));
        //    SystemLogManager::getInstance().log(SystemLogManager::LogType::Info, _T("验证时,密码错误!"));
        //    return 0;
        //}
        UserRole emRole = UserManager::getInstance().getCurrentUserRole();
        if (emRole != UserRole::SuperAdmin) {
            AfxMessageBox(_T("当前用户并非管理员!!!"));
            return 1;
        }
    }
    if (id == IDC_BUTTON_RUN) {
        if (theApp.m_model.getMaster().getState() == SERVO::MASTERSTATE::MSERROR) {
            AfxMessageBox("当前有机台发生错误,不能启动,请确认解决问题后再尝试重新启动!");
SourceCode/Bond/Servo/UserManager.cpp
@@ -1,4 +1,4 @@
#include "stdafx.h"
#include "stdafx.h"
#include "UserManager.h"
#include <chrono>
#include <iostream>
@@ -12,531 +12,531 @@
const std::string INITIAL_ADMIN_USERNAME = "admin";
const std::string INITIAL_ADMIN_PASSWORD = "admin";
// èŽ·å–å•ä¾‹å®žä¾‹
// èŽ·å–å•ä¾‹å®žä¾‹
UserManager& UserManager::getInstance() {
    static UserManager instance;
    return instance;
    static UserManager instance;
    return instance;
}
UserManager::UserManager()
    : m_isLoggedIn(false), m_isRememberMe(false), m_tmSessionTimeout(std::chrono::minutes(30)),
    m_tmSessionExpiration(std::chrono::hours(72)), m_hMouseHook(nullptr), m_hKeyboardHook(nullptr),
    m_pDB(std::make_unique<BL::SQLiteDatabase>()) {
    initializeDatabase();
    : m_isLoggedIn(false), m_isRememberMe(false), m_tmSessionTimeout(std::chrono::minutes(30)),
    m_tmSessionExpiration(std::chrono::hours(72)), m_hMouseHook(nullptr), m_hKeyboardHook(nullptr),
    m_pDB(std::make_unique<BL::SQLiteDatabase>()) {
    initializeDatabase();
}
UserManager::~UserManager() {
    terminateIdleDetection();
    terminateIdleDetection();
}
// æä¾›æ•°æ®åº“连接
// æä¾›æ•°æ®åº“连接
std::unique_ptr<BL::Database>& UserManager::getDatabaseInstance() {
    return m_pDB;
    return m_pDB;
}
// åˆå§‹åŒ–数据库,创建用户表并插入初始管理员用户
// åˆå§‹åŒ–数据库,创建用户表并插入初始管理员用户
bool UserManager::initializeDatabase() {
    std::string dbFilePath = getDatabaseFilePath();
    if (!m_pDB->connect(dbFilePath, true)) {
        throw std::runtime_error("Failed to connect to database.");
    }
    std::string dbFilePath = getDatabaseFilePath();
    if (!m_pDB->connect(dbFilePath, true)) {
        throw std::runtime_error("Failed to connect to database.");
    }
    std::string createTableQuery = R"(
        CREATE TABLE IF NOT EXISTS users (
            username VARCHAR(50) PRIMARY KEY,
            password VARCHAR(255) NOT NULL,
            role INT NOT NULL,
            session_timeout INT DEFAULT 30,
            session_expiration INT DEFAULT 72,
            last_login DATETIME DEFAULT (datetime('now', 'localtime'))
        )
    )";
    m_pDB->executeQuery(createTableQuery);
    std::string createTableQuery = R"(
        CREATE TABLE IF NOT EXISTS users (
            username VARCHAR(50) PRIMARY KEY,
            password VARCHAR(255) NOT NULL,
            role INT NOT NULL,
            session_timeout INT DEFAULT 30,
            session_expiration INT DEFAULT 72,
            last_login DATETIME DEFAULT (datetime('now', 'localtime'))
        )
    )";
    m_pDB->executeQuery(createTableQuery);
    std::string checkAdminQuery = "SELECT COUNT(*) FROM users WHERE role = 0";
    auto result = m_pDB->fetchResults(checkAdminQuery);
    std::string checkAdminQuery = "SELECT COUNT(*) FROM users WHERE role = 0";
    auto result = m_pDB->fetchResults(checkAdminQuery);
    if (result.empty() || result[0][0] == "0") {
        std::string insertAdminQuery = "INSERT INTO users (username, password, role, session_timeout, session_expiration) VALUES ('" +
            INITIAL_ADMIN_USERNAME + "', '" + simpleEncryptDecrypt(INITIAL_ADMIN_PASSWORD, "BandKey") + "', 0, 30, 72)";
        m_pDB->executeQuery(insertAdminQuery);
    }
    if (result.empty() || result[0][0] == "0") {
        std::string insertAdminQuery = "INSERT INTO users (username, password, role, session_timeout, session_expiration) VALUES ('" +
            INITIAL_ADMIN_USERNAME + "', '" + simpleEncryptDecrypt(INITIAL_ADMIN_PASSWORD, "BandKey") + "', 0, 30, 72)";
        m_pDB->executeQuery(insertAdminQuery);
    }
    return true;
    return true;
}
// å¯¹å¯†ç è¿›è¡Œå“ˆå¸Œå¤„理
// å¯¹å¯†ç è¿›è¡Œå“ˆå¸Œå¤„理
std::string UserManager::hashPassword(const std::string& password) {
    return std::to_string(std::hash<std::string>{}(password));
    return std::to_string(std::hash<std::string>{}(password));
}
// ç®€å•的加密和解密函数
// ç®€å•的加密和解密函数
std::string UserManager::simpleEncryptDecrypt(const std::string& data, const std::string& key) {
    std::string result = data;
    for (size_t i = 0; i < data.size(); ++i) {
        result[i] ^= key[i % key.size()];  // ç®€å•异或加密
    }
    return result;
    std::string result = data;
    for (size_t i = 0; i < data.size(); ++i) {
        result[i] ^= key[i % key.size()];  // ç®€å•异或加密
    }
    return result;
}
// ä»Žä¼šè¯æ–‡ä»¶åŠ è½½ä¼šè¯ä¿¡æ¯
// ä»Žä¼šè¯æ–‡ä»¶åŠ è½½ä¼šè¯ä¿¡æ¯
bool UserManager::loadSession() {
    std::ifstream sessionFile(getSessionFilePath(), std::ios::binary);
    if (!sessionFile.is_open()) {
        return false;
    }
    std::ifstream sessionFile(getSessionFilePath(), std::ios::binary);
    if (!sessionFile.is_open()) {
        return false;
    }
    // ä»Žæ–‡ä»¶è¯»å–加密数据
    std::string encryptedData((std::istreambuf_iterator<char>(sessionFile)), std::istreambuf_iterator<char>());
    sessionFile.close();
    // ä»Žæ–‡ä»¶è¯»å–加密数据
    std::string encryptedData((std::istreambuf_iterator<char>(sessionFile)), std::istreambuf_iterator<char>());
    sessionFile.close();
    // è§£å¯†æ•°æ®
    std::string decryptedData = simpleEncryptDecrypt(encryptedData, "my_secret_key");
    // è§£å¯†æ•°æ®
    std::string decryptedData = simpleEncryptDecrypt(encryptedData, "my_secret_key");
    // è§£æžè§£å¯†çš„æ•°æ®
    std::istringstream sessionData(decryptedData);
    std::string username;
    // è§£æžè§£å¯†çš„æ•°æ®
    std::istringstream sessionData(decryptedData);
    std::string username;
    std::string password;
    std::time_t lastLoginTime;
    int timeoutMinutes;
    int expirationHours;
    std::time_t lastLoginTime;
    int timeoutMinutes;
    int expirationHours;
    sessionData >> username >> password >> lastLoginTime >> timeoutMinutes >> expirationHours;
    sessionData >> username >> password >> lastLoginTime >> timeoutMinutes >> expirationHours;
    // éªŒè¯æ—¶é—´æˆ³æœ‰æ•ˆæ€§
    auto now = std::chrono::system_clock::now();
    auto lastLogin = std::chrono::system_clock::from_time_t(lastLoginTime);
    auto sessionDuration = std::chrono::duration_cast<std::chrono::hours>(now - lastLogin);
    // éªŒè¯æ—¶é—´æˆ³æœ‰æ•ˆæ€§
    auto now = std::chrono::system_clock::now();
    auto lastLogin = std::chrono::system_clock::from_time_t(lastLoginTime);
    auto sessionDuration = std::chrono::duration_cast<std::chrono::hours>(now - lastLogin);
    if (sessionDuration > std::chrono::hours(expirationHours)) {
        clearSession();
        return false;
    }
    if (sessionDuration > std::chrono::hours(expirationHours)) {
        clearSession();
        return false;
    }
    // æ¢å¤ä¼šè¯æ•°æ®
    m_strCurrentUser = username;
    // æ¢å¤ä¼šè¯æ•°æ®
    m_strCurrentUser = username;
    m_strCurrentPass = password;
    m_tpLastLogin = lastLogin;
    m_tmSessionTimeout = std::chrono::minutes(timeoutMinutes);
    m_tmSessionExpiration = std::chrono::hours(expirationHours);
    m_isLoggedIn = true;
    m_isRememberMe = true;
    updateActivityTime();
    m_tpLastLogin = lastLogin;
    m_tmSessionTimeout = std::chrono::minutes(timeoutMinutes);
    m_tmSessionExpiration = std::chrono::hours(expirationHours);
    m_isLoggedIn = true;
    m_isRememberMe = true;
    updateActivityTime();
    return true;
    return true;
}
// ä¿å­˜ä¼šè¯ä¿¡æ¯åˆ°æ–‡ä»¶
// ä¿å­˜ä¼šè¯ä¿¡æ¯åˆ°æ–‡ä»¶
void UserManager::saveSession() {
    if (!m_isRememberMe) {
        clearSession();
        return;
    }
    if (!m_isRememberMe) {
        clearSession();
        return;
    }
    // åŽŸå§‹ä¼šè¯æ•°æ®
    std::stringstream sessionData;
    std::time_t lastLoginTime = std::chrono::system_clock::to_time_t(m_tpLastLogin);
    sessionData << m_strCurrentUser << " " << m_strCurrentPass << " " << lastLoginTime << " "
        << m_tmSessionTimeout.count() << " " << m_tmSessionExpiration.count();
    // åŽŸå§‹ä¼šè¯æ•°æ®
    std::stringstream sessionData;
    std::time_t lastLoginTime = std::chrono::system_clock::to_time_t(m_tpLastLogin);
    sessionData << m_strCurrentUser << " " << m_strCurrentPass << " " << lastLoginTime << " "
        << m_tmSessionTimeout.count() << " " << m_tmSessionExpiration.count();
    // åŠ å¯†æ•°æ®
    std::string encryptedData = simpleEncryptDecrypt(sessionData.str(), "my_secret_key");
    // åŠ å¯†æ•°æ®
    std::string encryptedData = simpleEncryptDecrypt(sessionData.str(), "my_secret_key");
    // å†™å…¥åŠ å¯†æ•°æ®åˆ°æ–‡ä»¶
    std::ofstream sessionFile(getSessionFilePath(), std::ios::binary);
    if (sessionFile.is_open()) {
        sessionFile << encryptedData;
        sessionFile.close();
    }
    // å†™å…¥åŠ å¯†æ•°æ®åˆ°æ–‡ä»¶
    std::ofstream sessionFile(getSessionFilePath(), std::ios::binary);
    if (sessionFile.is_open()) {
        sessionFile << encryptedData;
        sessionFile.close();
    }
}
// æ¸…除会话文件
// æ¸…除会话文件
void UserManager::clearSession() {
    std::remove(getSessionFilePath().c_str());
    std::remove(getSessionFilePath().c_str());
}
// èŽ·å–ç¨‹åºè·¯å¾„ä¸‹çš„config文件夹路径
// èŽ·å–ç¨‹åºè·¯å¾„ä¸‹çš„config文件夹路径
std::string UserManager::getConfigFolderPath() {
    char szPath[MAX_PATH];
    GetModuleFileName(NULL, szPath, MAX_PATH);
    std::string exePath(szPath);
    std::string dbDir = exePath.substr(0, exePath.find_last_of("\\/")) + "\\DB\\";
    char szPath[MAX_PATH];
    GetModuleFileName(NULL, szPath, MAX_PATH);
    std::string exePath(szPath);
    std::string dbDir = exePath.substr(0, exePath.find_last_of("\\/")) + "\\DB\\";
    // æ£€æŸ¥å¹¶åˆ›å»ºconfig文件夹
    DWORD fileAttr = GetFileAttributes(dbDir.c_str());
    if (fileAttr == INVALID_FILE_ATTRIBUTES) {
        CreateDirectory(dbDir.c_str(), NULL);
    }
    // æ£€æŸ¥å¹¶åˆ›å»ºconfig文件夹
    DWORD fileAttr = GetFileAttributes(dbDir.c_str());
    if (fileAttr == INVALID_FILE_ATTRIBUTES) {
        CreateDirectory(dbDir.c_str(), NULL);
    }
    return dbDir;
    return dbDir;
}
// èŽ·å–session.dat文件路径
// èŽ·å–session.dat文件路径
std::string UserManager::getSessionFilePath() {
    return getConfigFolderPath() + SESSION_FILE;
    return getConfigFolderPath() + SESSION_FILE;
}
// èŽ·å–æ•°æ®åº“æ–‡ä»¶è·¯å¾„
// èŽ·å–æ•°æ®åº“æ–‡ä»¶è·¯å¾„
std::string UserManager::getDatabaseFilePath() {
    return getConfigFolderPath() + DATABASE_FILE;
    return getConfigFolderPath() + DATABASE_FILE;
}
// ç™»å½•方法
// ç™»å½•方法
bool UserManager::login(const std::string& username, const std::string& password, bool rememberMeFlag) {
    std::string query = "SELECT username, password, role, session_timeout, session_expiration FROM users WHERE username = '" + username + "'";
    auto result = m_pDB->fetchResults(query);
    std::string query = "SELECT username, password, role, session_timeout, session_expiration FROM users WHERE username = '" + username + "'";
    auto result = m_pDB->fetchResults(query);
    if (result.empty() || result[0][1] != simpleEncryptDecrypt(password, "BandKey")) {
        std::cerr << "Login failed: Invalid username or password." << std::endl;
        return false;
    }
    if (result.empty() || result[0][1] != simpleEncryptDecrypt(password, "BandKey")) {
        std::cerr << "Login failed: Invalid username or password." << std::endl;
        return false;
    }
    m_strCurrentUser = username;
    m_strCurrentUser = username;
    m_strCurrentPass = password;
    m_enCurrentUserRole = static_cast<UserRole>(std::stoi(result[0][2]));
    m_tmSessionTimeout = std::chrono::minutes(std::stoi(result[0][3]));
    m_tmSessionExpiration = std::chrono::hours(std::stoi(result[0][4]));
    m_isLoggedIn = true;
    m_isRememberMe = rememberMeFlag;
    updateActivityTime();
    m_tpLastLogin = std::chrono::system_clock::now();
    m_enCurrentUserRole = static_cast<UserRole>(std::stoi(result[0][2]));
    m_tmSessionTimeout = std::chrono::minutes(std::stoi(result[0][3]));
    m_tmSessionExpiration = std::chrono::hours(std::stoi(result[0][4]));
    m_isLoggedIn = true;
    m_isRememberMe = rememberMeFlag;
    updateActivityTime();
    m_tpLastLogin = std::chrono::system_clock::now();
    std::string updateLoginTime = "UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE username = '" + username + "'";
    m_pDB->executeQuery(updateLoginTime);
    std::string updateLoginTime = "UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE username = '" + username + "'";
    m_pDB->executeQuery(updateLoginTime);
    saveSession();
    return true;
    saveSession();
    return true;
}
// ç™»å‡ºæ–¹æ³•
// ç™»å‡ºæ–¹æ³•
void UserManager::logout() {
    if (m_isLoggedIn) {
        std::cout << "User logged out: " << m_strCurrentUser << std::endl;
        m_strCurrentUser.clear();
    if (m_isLoggedIn) {
        std::cout << "User logged out: " << m_strCurrentUser << std::endl;
        m_strCurrentUser.clear();
        m_strCurrentPass.clear();
        m_isLoggedIn = false;
        m_isRememberMe = false;
        clearSession();
    }
        m_isLoggedIn = false;
        m_isRememberMe = false;
        clearSession();
    }
}
// è¿”回当前用户的登录状态
// è¿”回当前用户的登录状态
bool UserManager::isLoggedIn() const {
    return m_isLoggedIn;
    return m_isLoggedIn;
}
// è¿”回当前用户的记住登录状态
// è¿”回当前用户的记住登录状态
bool UserManager::isRememberMe() const {
    return m_isRememberMe;
}
// åˆ›å»ºæ–°ç”¨æˆ·ï¼Œä»…超级管理员有权限
// åˆ›å»ºæ–°ç”¨æˆ·ï¼Œä»…超级管理员有权限
bool UserManager::createUser(const std::string& username, const std::string& password, UserRole role,
    std::chrono::minutes timeout, std::chrono::hours expiration) {
    if (m_enCurrentUserRole != UserRole::SuperAdmin) {
        std::cerr << "Only SuperAdmin can create new users." << std::endl;
        return false;
    }
    std::chrono::minutes timeout, std::chrono::hours expiration) {
    if (m_enCurrentUserRole != UserRole::SuperAdmin) {
        std::cerr << "Only SuperAdmin can create new users." << std::endl;
        return false;
    }
    std::string query = "INSERT INTO users (username, password, role, session_timeout, session_expiration) VALUES ('" +
        username + "', '" + simpleEncryptDecrypt(password, "BandKey") + "', " + std::to_string(static_cast<int>(role)) + ", " +
        std::to_string(timeout.count()) + ", " + std::to_string(expiration.count()) + ")";
    return m_pDB->executeQuery(query);
    std::string query = "INSERT INTO users (username, password, role, session_timeout, session_expiration) VALUES ('" +
        username + "', '" + simpleEncryptDecrypt(password, "BandKey") + "', " + std::to_string(static_cast<int>(role)) + ", " +
        std::to_string(timeout.count()) + ", " + std::to_string(expiration.count()) + ")";
    return m_pDB->executeQuery(query);
}
// åˆ é™¤ç”¨æˆ·ï¼Œä»…超级管理员有权限,且不能删除自己
// åˆ é™¤ç”¨æˆ·ï¼Œä»…超级管理员有权限,且不能删除自己
bool UserManager::deleteUser(const std::string& username) {
    if (m_enCurrentUserRole != UserRole::SuperAdmin) {
        std::cerr << "Only SuperAdmin can delete users." << std::endl;
        return false;
    }
    if (username == m_strCurrentUser) {
        std::cerr << "SuperAdmin cannot delete their own account." << std::endl;
        return false;
    }
    if (m_enCurrentUserRole != UserRole::SuperAdmin) {
        std::cerr << "Only SuperAdmin can delete users." << std::endl;
        return false;
    }
    if (username == m_strCurrentUser) {
        std::cerr << "SuperAdmin cannot delete their own account." << std::endl;
        return false;
    }
    std::string query = "DELETE FROM users WHERE username = '" + username + "'";
    return m_pDB->executeQuery(query);
    std::string query = "DELETE FROM users WHERE username = '" + username + "'";
    return m_pDB->executeQuery(query);
}
// èŽ·å–æ‰€æœ‰ç”¨æˆ·ä¿¡æ¯ï¼Œä»…è¶…çº§ç®¡ç†å‘˜æœ‰æƒé™
// èŽ·å–æ‰€æœ‰ç”¨æˆ·ä¿¡æ¯ï¼Œä»…è¶…çº§ç®¡ç†å‘˜æœ‰æƒé™
std::vector<std::vector<std::string>> UserManager::getUsers() {
    if (m_enCurrentUserRole != UserRole::SuperAdmin) {
        std::cerr << "Only SuperAdmin can retrieve user data." << std::endl;
        return {};
    }
    if (m_enCurrentUserRole != UserRole::SuperAdmin) {
        std::cerr << "Only SuperAdmin can retrieve user data." << std::endl;
        return {};
    }
    // æŸ¥è¯¢æ•´ä¸ªç”¨æˆ·è¡¨
    std::string query = "SELECT username, password, role, session_timeout, session_expiration, last_login FROM users";
    std::vector<std::vector<std::string>> results = m_pDB->fetchResults(query);
    for (auto& row : results) {
        row[1] = simpleEncryptDecrypt(row[1], "BandKey");
    }
    // æŸ¥è¯¢æ•´ä¸ªç”¨æˆ·è¡¨
    std::string query = "SELECT username, password, role, session_timeout, session_expiration, last_login FROM users";
    std::vector<std::vector<std::string>> results = m_pDB->fetchResults(query);
    for (auto& row : results) {
        row[1] = simpleEncryptDecrypt(row[1], "BandKey");
    }
    return results;
    return results;
}
// è®¾ç½®æ•´ä¸ªç”¨æˆ·è¡¨çš„æ•°æ®ï¼Œä»…超级管理员有权限
// è®¾ç½®æ•´ä¸ªç”¨æˆ·è¡¨çš„æ•°æ®ï¼Œä»…超级管理员有权限
bool UserManager::setUsers(const std::vector<std::vector<std::string>>& usersData) {
    if (m_enCurrentUserRole != UserRole::SuperAdmin) {
        std::cerr << "Only SuperAdmin can set user data." << std::endl;
        return false;
    }
    if (m_enCurrentUserRole != UserRole::SuperAdmin) {
        std::cerr << "Only SuperAdmin can set user data." << std::endl;
        return false;
    }
    // æ¸…空用户表
    std::string deleteQuery = "DELETE FROM users";
    if (!m_pDB->executeQuery(deleteQuery)) {
        std::cerr << "Failed to clear the users table." << std::endl;
        return false;
    }
    // æ¸…空用户表
    std::string deleteQuery = "DELETE FROM users";
    if (!m_pDB->executeQuery(deleteQuery)) {
        std::cerr << "Failed to clear the users table." << std::endl;
        return false;
    }
    // æ’入新的用户数据
    for (const auto& user : usersData) {
        if (user.size() != 6) {
            std::cerr << "Invalid data format for user. Each user must have 6 fields." << std::endl;
            return false;
        }
    // æ’入新的用户数据
    for (const auto& user : usersData) {
        if (user.size() != 6) {
            std::cerr << "Invalid data format for user. Each user must have 6 fields." << std::endl;
            return false;
        }
        std::string insertQuery = "INSERT INTO users (username, password, role, session_timeout, session_expiration, last_login) VALUES ('" +
            user[0] + "', '" + simpleEncryptDecrypt(user[1], "BandKey") + "', " + user[2] + ", " + user[3] + ", " + user[4] + ", '" + user[5] + "')";
        std::string insertQuery = "INSERT INTO users (username, password, role, session_timeout, session_expiration, last_login) VALUES ('" +
            user[0] + "', '" + simpleEncryptDecrypt(user[1], "BandKey") + "', " + user[2] + ", " + user[3] + ", " + user[4] + ", '" + user[5] + "')";
        if (!m_pDB->executeQuery(insertQuery)) {
            std::cerr << "Failed to insert user: " << user[0] << std::endl;
            return false;
        }
    }
        if (!m_pDB->executeQuery(insertQuery)) {
            std::cerr << "Failed to insert user: " << user[0] << std::endl;
            return false;
        }
    }
    return true;
    return true;
}
// ä¿®æ”¹ç”¨æˆ·åï¼Œä»…超级管理员有权限
// ä¿®æ”¹ç”¨æˆ·åï¼Œä»…超级管理员有权限
bool UserManager::changeUsername(const std::string& username, const std::string& newUsername) {
    if (m_enCurrentUserRole != UserRole::SuperAdmin) {
        std::cerr << "Only SuperAdmin can change usernames." << std::endl;
        return false;
    }
    if (m_enCurrentUserRole != UserRole::SuperAdmin) {
        std::cerr << "Only SuperAdmin can change usernames." << std::endl;
        return false;
    }
    std::string query = "UPDATE users SET username = '" + newUsername + "' WHERE username = '" + username + "'";
    bool success = m_pDB->executeQuery(query);
    std::string query = "UPDATE users SET username = '" + newUsername + "' WHERE username = '" + username + "'";
    bool success = m_pDB->executeQuery(query);
    // å¦‚果是当前登录用户修改自己的用户名,更新成员变量并保存会话文件
    if (success && m_strCurrentUser == username) {
        m_strCurrentUser = newUsername;
    // å¦‚果是当前登录用户修改自己的用户名,更新成员变量并保存会话文件
    if (success && m_strCurrentUser == username) {
        m_strCurrentUser = newUsername;
        // å¦‚果“记住密码”已启用,更新会话文件
        if (m_isRememberMe) {
            saveSession();
        }
    }
    return success;
        // å¦‚果“记住密码”已启用,更新会话文件
        if (m_isRememberMe) {
            saveSession();
        }
    }
    return success;
}
// ä¿®æ”¹ç”¨æˆ·å¯†ç ï¼ˆä»…允许当前用户或超级管理员)
// ä¿®æ”¹ç”¨æˆ·å¯†ç ï¼ˆä»…允许当前用户或超级管理员)
bool UserManager::changePassword(const std::string& username, const std::string& newPassword) {
    if (username != m_strCurrentUser && m_enCurrentUserRole != UserRole::SuperAdmin) {
        std::cerr << "Permission denied: Only the user or SuperAdmin can change passwords." << std::endl;
        return false;
    }
    if (username != m_strCurrentUser && m_enCurrentUserRole != UserRole::SuperAdmin) {
        std::cerr << "Permission denied: Only the user or SuperAdmin can change passwords." << std::endl;
        return false;
    }
    std::string query = "UPDATE users SET password = '" + simpleEncryptDecrypt(newPassword, "BandKey") +
        "' WHERE username = '" + username + "'";
    bool success = m_pDB->executeQuery(query);
    std::string query = "UPDATE users SET password = '" + simpleEncryptDecrypt(newPassword, "BandKey") +
        "' WHERE username = '" + username + "'";
    bool success = m_pDB->executeQuery(query);
    // å¦‚果是当前用户修改自己的密码,退出登录并清除会话文件
    if (success && m_strCurrentUser == username) {
        logout();
        std::cout << "Password changed successfully. Please log in again." << std::endl;
    }
    // å¦‚果是当前用户修改自己的密码,退出登录并清除会话文件
    if (success && m_strCurrentUser == username) {
        logout();
        std::cout << "Password changed successfully. Please log in again." << std::endl;
    }
    return success;
    return success;
}
// æ›´æ”¹ç”¨æˆ·è§’色,仅超级管理员有权限
// æ›´æ”¹ç”¨æˆ·è§’色,仅超级管理员有权限
bool UserManager::changeUserRole(const std::string& username, UserRole newRole) {
    if (m_enCurrentUserRole != UserRole::SuperAdmin) {
        std::cerr << "Only SuperAdmin can change user roles." << std::endl;
        return false;
    }
    if (m_enCurrentUserRole != UserRole::SuperAdmin) {
        std::cerr << "Only SuperAdmin can change user roles." << std::endl;
        return false;
    }
    // é˜²æ­¢ç®¡ç†å‘˜æ›´æ”¹è‡ªå·±çš„角色
    if (m_strCurrentUser == username) {
        std::cerr << "SuperAdmin cannot change their own role." << std::endl;
        return false;
    }
    // é˜²æ­¢ç®¡ç†å‘˜æ›´æ”¹è‡ªå·±çš„角色
    if (m_strCurrentUser == username) {
        std::cerr << "SuperAdmin cannot change their own role." << std::endl;
        return false;
    }
    std::string query = "UPDATE users SET role = " + std::to_string(static_cast<int>(newRole)) +
        " WHERE username = '" + username + "'";
    return m_pDB->executeQuery(query);
    std::string query = "UPDATE users SET role = " + std::to_string(static_cast<int>(newRole)) +
        " WHERE username = '" + username + "'";
    return m_pDB->executeQuery(query);
}
// ä¿®æ”¹ç”¨æˆ·çš„ session_timeout,仅超级管理员有权限
// ä¿®æ”¹ç”¨æˆ·çš„ session_timeout,仅超级管理员有权限
bool UserManager::changeUserSessionTimeout(const std::string& username, int newTimeoutMinutes) {
    if (m_enCurrentUserRole != UserRole::SuperAdmin) {
        std::cerr << "Only SuperAdmin can change session timeout." << std::endl;
        return false;
    }
    if (m_enCurrentUserRole != UserRole::SuperAdmin) {
        std::cerr << "Only SuperAdmin can change session timeout." << std::endl;
        return false;
    }
    std::string query = "UPDATE users SET session_timeout = " + std::to_string(newTimeoutMinutes) +
        " WHERE username = '" + username + "'";
    bool success = m_pDB->executeQuery(query);
    // å¦‚果是当前登录用户修改自己的超时设置,更新成员变量
    if (success && m_strCurrentUser == username) {
        m_tmSessionTimeout = std::chrono::minutes(newTimeoutMinutes);
    std::string query = "UPDATE users SET session_timeout = " + std::to_string(newTimeoutMinutes) +
        " WHERE username = '" + username + "'";
    bool success = m_pDB->executeQuery(query);
    // å¦‚果是当前登录用户修改自己的超时设置,更新成员变量
    if (success && m_strCurrentUser == username) {
        m_tmSessionTimeout = std::chrono::minutes(newTimeoutMinutes);
        if (m_isRememberMe) {
            saveSession();
        }
    }
    return success;
        if (m_isRememberMe) {
            saveSession();
        }
    }
    return success;
}
// ä¿®æ”¹ç”¨æˆ·çš„ session_expiration,仅超级管理员有权限
// ä¿®æ”¹ç”¨æˆ·çš„ session_expiration,仅超级管理员有权限
bool UserManager::changeUserSessionExpiration(const std::string& username, int newExpirationHours) {
    if (m_enCurrentUserRole != UserRole::SuperAdmin) {
        std::cerr << "Only SuperAdmin can change session expiration." << std::endl;
        return false;
    }
    if (m_enCurrentUserRole != UserRole::SuperAdmin) {
        std::cerr << "Only SuperAdmin can change session expiration." << std::endl;
        return false;
    }
    std::string query = "UPDATE users SET session_expiration = " + std::to_string(newExpirationHours) +
        " WHERE username = '" + username + "'";
    bool success = m_pDB->executeQuery(query);
    // å¦‚果是当前登录用户修改自己的过期设置,更新成员变量
    if (success && m_strCurrentUser == username) {
        m_tmSessionExpiration = std::chrono::hours(newExpirationHours);
    std::string query = "UPDATE users SET session_expiration = " + std::to_string(newExpirationHours) +
        " WHERE username = '" + username + "'";
    bool success = m_pDB->executeQuery(query);
    // å¦‚果是当前登录用户修改自己的过期设置,更新成员变量
    if (success && m_strCurrentUser == username) {
        m_tmSessionExpiration = std::chrono::hours(newExpirationHours);
        if (m_isRememberMe) {
            saveSession();
        }
    }
    return success;
        if (m_isRememberMe) {
            saveSession();
        }
    }
    return success;
}
// èŽ·å–æ‰€æœ‰ç”¨æˆ·åç§°
// èŽ·å–æ‰€æœ‰ç”¨æˆ·åç§°
std::vector<std::string> UserManager::getUsernames() {
    std::vector<std::string> usernames;
    std::string query = "SELECT username FROM users";
    auto results = m_pDB->fetchResults(query);
    std::vector<std::string> usernames;
    std::string query = "SELECT username FROM users";
    auto results = m_pDB->fetchResults(query);
    for (const auto& row : results) {
        if (!row.empty()) {
            usernames.push_back(row[0]); // èŽ·å–ç”¨æˆ·ååˆ—çš„å€¼
        }
    }
    for (const auto& row : results) {
        if (!row.empty()) {
            usernames.push_back(row[0]); // èŽ·å–ç”¨æˆ·ååˆ—çš„å€¼
        }
    }
    return usernames;
    return usernames;
}
// èŽ·å–æŒ‡å®šç”¨æˆ·åçš„ç”¨æˆ·ä¿¡æ¯
// èŽ·å–æŒ‡å®šç”¨æˆ·åçš„ç”¨æˆ·ä¿¡æ¯
std::vector<std::string> UserManager::getUserInfo(const std::string& username)
{
    // æž„建查询语句
    std::ostringstream query;
    query << "SELECT username, password, role, session_timeout, session_expiration, last_login "
        << "FROM users WHERE username = '" << username << "'";
    // æž„建查询语句
    std::ostringstream query;
    query << "SELECT username, password, role, session_timeout, session_expiration, last_login "
        << "FROM users WHERE username = '" << username << "'";
    // æ‰§è¡ŒæŸ¥è¯¢å¹¶èŽ·å–ç»“æžœ
    auto results = m_pDB->fetchResults(query.str());
    if (results.empty()) {
        return {};
    }
    // æ‰§è¡ŒæŸ¥è¯¢å¹¶èŽ·å–ç»“æžœ
    auto results = m_pDB->fetchResults(query.str());
    if (results.empty()) {
        return {};
    }
    // è¿”回查询到的第一行数据
    return results[0];
    // è¿”回查询到的第一行数据
    return results[0];
}
// æ›´æ–°æœ€åŽæ´»åŠ¨æ—¶é—´ï¼Œç”¨äºŽæ— æ“ä½œè¶…æ—¶æ£€æµ‹
// æ›´æ–°æœ€åŽæ´»åŠ¨æ—¶é—´ï¼Œç”¨äºŽæ— æ“ä½œè¶…æ—¶æ£€æµ‹
void UserManager::updateActivityTime() {
    m_tpLastActivity = std::chrono::system_clock::now();
    std::cout << "Activity updated at: " << std::chrono::system_clock::to_time_t(m_tpLastActivity) << std::endl;
    m_tpLastActivity = std::chrono::system_clock::now();
    std::cout << "Activity updated at: " << std::chrono::system_clock::to_time_t(m_tpLastActivity) << std::endl;
}
// è®¾ç½®æ— æ“ä½œè¶…æ—¶æ—¶é—´
// è®¾ç½®æ— æ“ä½œè¶…æ—¶æ—¶é—´
void UserManager::setSessionTimeout(std::chrono::minutes timeout) {
    m_tmSessionTimeout = timeout;
    m_tmSessionTimeout = timeout;
}
// æ£€æŸ¥æ˜¯å¦è¶…过无操作超时时间
// æ£€æŸ¥æ˜¯å¦è¶…过无操作超时时间
bool UserManager::isInactiveTimeout() const {
    auto now = std::chrono::system_clock::now();
    auto elapsedSeconds = std::chrono::duration_cast<std::chrono::seconds>(now - m_tpLastActivity).count();
    return elapsedSeconds > m_tmSessionTimeout.count() * 60;
    auto now = std::chrono::system_clock::now();
    auto elapsedSeconds = std::chrono::duration_cast<std::chrono::seconds>(now - m_tpLastActivity).count();
    return elapsedSeconds > m_tmSessionTimeout.count() * 60;
}
// åˆå§‹åŒ–无操作检测,包括设置全局鼠标和键盘钩子
// åˆå§‹åŒ–无操作检测,包括设置全局鼠标和键盘钩子
void UserManager::initializeIdleDetection(HWND hwnd) {
    updateActivityTime();
    m_hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, LowLevelMouseProc, (HINSTANCE) nullptr, 0);
    m_hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, (HINSTANCE) nullptr, 0);
    ::SetTimer(hwnd, 1, 60000, nullptr);
    updateActivityTime();
    m_hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, LowLevelMouseProc, (HINSTANCE) nullptr, 0);
    m_hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, (HINSTANCE) nullptr, 0);
    ::SetTimer(hwnd, 1, 60000, nullptr);
}
// ç»ˆæ­¢æ— æ“ä½œæ£€æµ‹ï¼Œæ¸…除鼠标和键盘钩子
// ç»ˆæ­¢æ— æ“ä½œæ£€æµ‹ï¼Œæ¸…除鼠标和键盘钩子
void UserManager::terminateIdleDetection() {
    if (m_hMouseHook) {
        UnhookWindowsHookEx(m_hMouseHook);
        m_hMouseHook = nullptr;
    }
    if (m_hKeyboardHook) {
        UnhookWindowsHookEx(m_hKeyboardHook);
        m_hKeyboardHook = nullptr;
    }
    ::KillTimer(nullptr, 1);
    if (m_hMouseHook) {
        UnhookWindowsHookEx(m_hMouseHook);
        m_hMouseHook = nullptr;
    }
    if (m_hKeyboardHook) {
        UnhookWindowsHookEx(m_hKeyboardHook);
        m_hKeyboardHook = nullptr;
    }
    ::KillTimer(nullptr, 1);
}
// èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·å
// èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·å
std::string UserManager::getCurrentUser() const {
    return m_strCurrentUser;
}
// ä¿®æ”¹å½“前登录用户名
// ä¿®æ”¹å½“前登录用户名
void UserManager::setCurrentUser(const std::string& strName) {
    m_strCurrentUser = strName;
    m_strCurrentUser = strName;
}
// èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·å¯†ç 
// èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·å¯†ç 
std::string UserManager::getCurrentPass() const {
    return m_strCurrentPass;
}
// ä¿®æ”¹å½“前登录用户密码
// ä¿®æ”¹å½“前登录用户密码
void UserManager::setCurrentPass(const std::string& strPass) {
    m_strCurrentPass = strPass;
    m_strCurrentPass = strPass;
}
// èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·è§’è‰²
// èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·è§’è‰²
UserRole UserManager::getCurrentUserRole() const {
    return m_enCurrentUserRole;
}
// ä¿®æ”¹å½“前登录用户角色
// ä¿®æ”¹å½“前登录用户角色
void UserManager::setCurrentUserRole(UserRole emRole) {
    m_enCurrentUserRole = emRole;
    m_enCurrentUserRole = emRole;
}
// èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·çš„æ— æ“ä½œè¶…æ—¶æ—¶é—´
// èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·çš„æ— æ“ä½œè¶…æ—¶æ—¶é—´
std::chrono::minutes UserManager::getSessionTimeout() const {
    return m_tmSessionTimeout;
}
// èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·çš„ä¼šè¯è¿‡æœŸæ—¶é—´
// èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·çš„ä¼šè¯è¿‡æœŸæ—¶é—´
std::chrono::hours UserManager::getSessionExpiration() const {
    return m_tmSessionExpiration;
}
// å…¨å±€é¼ æ ‡é’©å­å›žè°ƒï¼Œè®°å½•活动时间
// å…¨å±€é¼ æ ‡é’©å­å›žè°ƒï¼Œè®°å½•活动时间
LRESULT CALLBACK UserManager::LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode == HC_ACTION) {
        UserManager::getInstance().updateActivityTime();
        std::cout << "Mouse event detected. Activity time updated." << std::endl;
    }
    return CallNextHookEx(nullptr, nCode, wParam, lParam);
    if (nCode == HC_ACTION) {
        UserManager::getInstance().updateActivityTime();
        std::cout << "Mouse event detected. Activity time updated." << std::endl;
    }
    return CallNextHookEx(nullptr, nCode, wParam, lParam);
}
// å…¨å±€é”®ç›˜é’©å­å›žè°ƒï¼Œè®°å½•活动时间
// å…¨å±€é”®ç›˜é’©å­å›žè°ƒï¼Œè®°å½•活动时间
LRESULT CALLBACK UserManager::LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode == HC_ACTION) {
        UserManager::getInstance().updateActivityTime();
        std::cout << "Keyboard event detected. Activity time updated." << std::endl;
    }
    return CallNextHookEx(nullptr, nCode, wParam, lParam);
    if (nCode == HC_ACTION) {
        UserManager::getInstance().updateActivityTime();
        std::cout << "Keyboard event detected. Activity time updated." << std::endl;
    }
    return CallNextHookEx(nullptr, nCode, wParam, lParam);
}
SourceCode/Bond/Servo/UserManager.h
@@ -1,4 +1,4 @@
#ifndef USER_MANAGER_H
#ifndef USER_MANAGER_H
#define USER_MANAGER_H
#include <string>
@@ -7,124 +7,126 @@
#include <windows.h>
#include "Database.h"
// ç”¨æˆ·è§’色定义
// ç”¨æˆ·è§’色定义
enum class UserRole {
    SuperAdmin = 0,     // è¶…级管理员
    Engineer,           // å·¥ç¨‹å¸ˆ
    Operator            // æ“ä½œå‘˜
    SuperAdmin = 0,   // è¶…级管理员:系统最高权限,管理所有用户和权限
    ProcessEngineer,  // åˆ¶ç¨‹å·¥ç¨‹å¸ˆï¼šè´Ÿè´£å·¥è‰ºåˆ¶å®šä¸Žä¼˜åŒ–
    EquipmentEngineer,// è®¾å¤‡å·¥ç¨‹å¸ˆï¼šè´Ÿè´£è®¾å¤‡ç»´æŠ¤ä¸ŽæŠ€æœ¯æ”¯æŒ
    Operator,         // æ“ä½œå‘˜ï¼šæ‰§è¡Œæ—¥å¸¸ç”Ÿäº§æ“ä½œ
    Unknown           // æœªçŸ¥è§’色:默认或未识别的角色
};
// ç”¨æˆ·ç®¡ç†ç±»ï¼Œé‡‡ç”¨å•例模式
// ç”¨æˆ·ç®¡ç†ç±»ï¼Œé‡‡ç”¨å•例模式
class UserManager {
public:
    static UserManager& getInstance();
    static UserManager& getInstance();
    UserManager(const UserManager&) = delete;
    UserManager& operator=(const UserManager&) = delete;
    UserManager(const UserManager&) = delete;
    UserManager& operator=(const UserManager&) = delete;
    // æä¾›æ•°æ®åº“连接
    std::unique_ptr<BL::Database>& getDatabaseInstance();
    // æä¾›æ•°æ®åº“连接
    std::unique_ptr<BL::Database>& getDatabaseInstance();
    // ç”¨æˆ·æ“ä½œ
    bool login(const std::string& username, const std::string& password, bool rememberMe = false);
    void logout();
    bool isLoggedIn() const;
    // ç”¨æˆ·æ“ä½œ
    bool login(const std::string& username, const std::string& password, bool rememberMe = false);
    void logout();
    bool isLoggedIn() const;
    bool isRememberMe() const;
    bool createUser(const std::string& username, const std::string& password, UserRole role,
        std::chrono::minutes timeout = std::chrono::minutes(30),
        std::chrono::hours expiration = std::chrono::hours(72));
    bool deleteUser(const std::string& username);
    std::vector<std::vector<std::string>> getUsers();
    bool setUsers(const std::vector<std::vector<std::string>>& usersData);
    bool changeUsername(const std::string& username, const std::string& newUsername);
    bool changePassword(const std::string& username, const std::string& newPassword);
    bool changeUserRole(const std::string& username, UserRole newRole);
    bool changeUserSessionTimeout(const std::string& username, int newTimeoutMinutes);
    bool changeUserSessionExpiration(const std::string& username, int newExpirationHours);
    std::vector<std::string> getUsernames();
    std::vector<std::string> getUserInfo(const std::string& username);
    bool createUser(const std::string& username, const std::string& password, UserRole role,
        std::chrono::minutes timeout = std::chrono::minutes(30),
        std::chrono::hours expiration = std::chrono::hours(72));
    bool deleteUser(const std::string& username);
    std::vector<std::vector<std::string>> getUsers();
    bool setUsers(const std::vector<std::vector<std::string>>& usersData);
    bool changeUsername(const std::string& username, const std::string& newUsername);
    bool changePassword(const std::string& username, const std::string& newPassword);
    bool changeUserRole(const std::string& username, UserRole newRole);
    bool changeUserSessionTimeout(const std::string& username, int newTimeoutMinutes);
    bool changeUserSessionExpiration(const std::string& username, int newExpirationHours);
    std::vector<std::string> getUsernames();
    std::vector<std::string> getUserInfo(const std::string& username);
    // ä¼šè¯æ–‡ä»¶æ“ä½œ
    bool loadSession();     // ä»Žä¼šè¯æ–‡ä»¶åŠ è½½ä¼šè¯ä¿¡æ¯
    void saveSession();     // ä¿å­˜ä¼šè¯ä¿¡æ¯åˆ°æ–‡ä»¶
    void clearSession();    // æ¸…除会话文件
    // ä¼šè¯æ–‡ä»¶æ“ä½œ
    bool loadSession();     // ä»Žä¼šè¯æ–‡ä»¶åŠ è½½ä¼šè¯ä¿¡æ¯
    void saveSession();     // ä¿å­˜ä¼šè¯ä¿¡æ¯åˆ°æ–‡ä»¶
    void clearSession();    // æ¸…除会话文件
    // é…ç½®æ–‡ä»¶å¤¹è·¯å¾„管理
    static std::string getConfigFolderPath();
    static std::string getSessionFilePath();
    static std::string getDatabaseFilePath();
    // é…ç½®æ–‡ä»¶å¤¹è·¯å¾„管理
    static std::string getConfigFolderPath();
    static std::string getSessionFilePath();
    static std::string getDatabaseFilePath();
    // æ›´æ–°æœ€åŽæ´»åŠ¨æ—¶é—´ï¼ˆç”¨äºŽæ— æ“ä½œè¶…æ—¶æ£€æµ‹ï¼‰
    void updateActivityTime();
    // æ›´æ–°æœ€åŽæ´»åŠ¨æ—¶é—´ï¼ˆç”¨äºŽæ— æ“ä½œè¶…æ—¶æ£€æµ‹ï¼‰
    void updateActivityTime();
    // è®¾ç½®ç”¨æˆ·çš„æ— æ“ä½œè¶…æ—¶æ—¶é—´
    void setSessionTimeout(std::chrono::minutes timeout);
    // è®¾ç½®ç”¨æˆ·çš„æ— æ“ä½œè¶…æ—¶æ—¶é—´
    void setSessionTimeout(std::chrono::minutes timeout);
    // æ£€æŸ¥æ˜¯å¦æ— æ“ä½œè¶…æ—¶
    bool isInactiveTimeout() const;
    // æ£€æŸ¥æ˜¯å¦æ— æ“ä½œè¶…æ—¶
    bool isInactiveTimeout() const;
    // åˆå§‹åŒ–无操作检测(设置全局钩子和定时器)
    void initializeIdleDetection(HWND hwnd);
    // åˆå§‹åŒ–无操作检测(设置全局钩子和定时器)
    void initializeIdleDetection(HWND hwnd);
    // ç»ˆæ­¢æ— æ“ä½œæ£€æµ‹ï¼ˆæ¸…除钩子和定时器)
    void terminateIdleDetection();
    // ç»ˆæ­¢æ— æ“ä½œæ£€æµ‹ï¼ˆæ¸…除钩子和定时器)
    void terminateIdleDetection();
    // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·å
    // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·å
    std::string getCurrentUser() const;
    // ä¿®æ”¹å½“前登录用户名
    void setCurrentUser(const std::string& strName);
    // ä¿®æ”¹å½“前登录用户名
    void setCurrentUser(const std::string& strName);
    // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·å¯†ç 
    // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·å¯†ç 
    std::string getCurrentPass() const;
    // ä¿®æ”¹å½“前登录用户密码
    void setCurrentPass(const std::string& strPass);
    // ä¿®æ”¹å½“前登录用户密码
    void setCurrentPass(const std::string& strPass);
    // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·è§’è‰²
    // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·è§’è‰²
    UserRole getCurrentUserRole() const;
    // ä¿®æ”¹å½“前登录用户角色
    void setCurrentUserRole(UserRole emRole);
    // ä¿®æ”¹å½“前登录用户角色
    void setCurrentUserRole(UserRole emRole);
    // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·çš„æ— æ“ä½œè¶…æ—¶æ—¶é—´
    // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·çš„æ— æ“ä½œè¶…æ—¶æ—¶é—´
    std::chrono::minutes getSessionTimeout() const;
    // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·çš„ä¼šè¯è¿‡æœŸæ—¶é—´
    // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·çš„ä¼šè¯è¿‡æœŸæ—¶é—´
    std::chrono::hours getSessionExpiration() const;
private:
    UserManager();
    ~UserManager();
    UserManager();
    ~UserManager();
    // åˆå§‹åŒ–数据库连接和用户表
    bool initializeDatabase();
    // åˆå§‹åŒ–数据库连接和用户表
    bool initializeDatabase();
    // å“ˆå¸Œå¯†ç ï¼Œç”¨äºŽåŠ å¯†ç”¨æˆ·å¯†ç 
    std::string hashPassword(const std::string& password);
    // å“ˆå¸Œå¯†ç ï¼Œç”¨äºŽåŠ å¯†ç”¨æˆ·å¯†ç 
    std::string hashPassword(const std::string& password);
    // åŠ å¯†å’Œè§£å¯†å‡½æ•°
    std::string simpleEncryptDecrypt(const std::string& data, const std::string& key);
    // åŠ å¯†å’Œè§£å¯†å‡½æ•°
    std::string simpleEncryptDecrypt(const std::string& data, const std::string& key);
    // é”®ç›˜å’Œé¼ æ ‡é’©å­å‡½æ•°
    static LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam);
    static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
    // é”®ç›˜å’Œé¼ æ ‡é’©å­å‡½æ•°
    static LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam);
    static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
    // å±žæ€§å®šä¹‰
    std::string m_strCurrentUser;                     // å½“前登录用户名
    std::string m_strCurrentPass;                     // å½“前登录密码
    UserRole m_enCurrentUserRole;                     // å½“前登录用户角色
    bool m_isLoggedIn;                                // æ˜¯å¦å·²ç™»å½•
    bool m_isRememberMe;                              // æ˜¯å¦è®°ä½ç™»å½•状态
    // å±žæ€§å®šä¹‰
    std::string m_strCurrentUser;                     // å½“前登录用户名
    std::string m_strCurrentPass;                     // å½“前登录密码
    UserRole m_enCurrentUserRole;                     // å½“前登录用户角色
    bool m_isLoggedIn;                                // æ˜¯å¦å·²ç™»å½•
    bool m_isRememberMe;                              // æ˜¯å¦è®°ä½ç™»å½•状态
    std::chrono::time_point<std::chrono::system_clock> m_tpLastLogin;     // ä¸Šæ¬¡ç™»å½•æ—¶é—´
    std::chrono::time_point<std::chrono::system_clock> m_tpLastActivity;  // æœ€åŽæ´»åŠ¨æ—¶é—´
    std::chrono::minutes m_tmSessionTimeout;          // æ— æ“ä½œè¶…æ—¶æ—¶é—´
    std::chrono::hours m_tmSessionExpiration;         // ä¼šè¯è¿‡æœŸæ—¶é—´
    HHOOK m_hMouseHook;                               // é¼ æ ‡é’©å­å¥æŸ„
    HHOOK m_hKeyboardHook;                            // é”®ç›˜é’©å­å¥æŸ„
    std::chrono::time_point<std::chrono::system_clock> m_tpLastLogin;     // ä¸Šæ¬¡ç™»å½•æ—¶é—´
    std::chrono::time_point<std::chrono::system_clock> m_tpLastActivity;  // æœ€åŽæ´»åŠ¨æ—¶é—´
    std::chrono::minutes m_tmSessionTimeout;          // æ— æ“ä½œè¶…æ—¶æ—¶é—´
    std::chrono::hours m_tmSessionExpiration;         // ä¼šè¯è¿‡æœŸæ—¶é—´
    HHOOK m_hMouseHook;                               // é¼ æ ‡é’©å­å¥æŸ„
    HHOOK m_hKeyboardHook;                            // é”®ç›˜é’©å­å¥æŸ„
    std::unique_ptr<BL::Database> m_pDB;              // æ•°æ®åº“接口
    std::unique_ptr<BL::Database> m_pDB;              // æ•°æ®åº“接口
};
#endif // USER_MANAGER_H
SourceCode/Bond/Servo/UserManagerDlg.cpp
@@ -88,9 +88,10 @@
    m_gridUserManager.ExpandLastColumn();                // æœ€åŽä¸€åˆ—填充网格
    m_mapRoleDescriptions.clear();
    m_mapRoleDescriptions.emplace(_T("管理员"), _T("管理所有用户,分配权限"));
    m_mapRoleDescriptions.emplace(_T("工程师"), _T("维护系统,解决技术问题"));
    m_mapRoleDescriptions.emplace(_T("操作员"), _T("执行日常操作任务"));
    m_mapRoleDescriptions.emplace(_T("管理员"), _T("管理所有用户账户,分配和调整权限,负责系统安全与整体运行"));
    m_mapRoleDescriptions.emplace(_T("制程工程师"), _T("负责生产工艺的制定、优化与改进,确保工艺稳定和良率提升"));
    m_mapRoleDescriptions.emplace(_T("设备工程师"), _T("维护和保养设备,处理故障,保障设备稳定运行,参与技术升级"));
    m_mapRoleDescriptions.emplace(_T("操作员"), _T("按照标准流程执行日常操作任务,监控生产状况,及时反馈异常"));
    FillUserManager();
}
@@ -134,12 +135,13 @@
    CStringArray permissions;
    permissions.Add(_T("管理员"));
    permissions.Add(_T("工程师"));
    permissions.Add(_T("制程工程师"));
    permissions.Add(_T("设备工程师"));
    permissions.Add(_T("操作员"));
    int nCols = m_gridUserManager.GetColumnCount();
    for (int i = 1; i < m_gridUserManager.GetRowCount(); ++i) {
        m_gridUserManager.SetItemState(i, 0, GVIS_READONLY); // ç¬¬ä¸€åˆ—只读
        m_gridUserManager.SetItemState(i, 0, GVIS_READONLY);         // ç¬¬ä¸€åˆ—只读
        m_gridUserManager.SetItemState(i, nCols - 2, GVIS_READONLY); // å€’数第二列只读
        m_gridUserManager.SetItemState(i, nCols - 1, GVIS_READONLY); // æœ€åŽä¸€åˆ—只读
@@ -149,13 +151,12 @@
            pCell->SetOptions(permissions);
            pCell->SetStyle(CBS_DROPDOWNLIST);
            CString cstrRole = m_gridUserManager.GetItemText(i, 3);
            int nRole = _ttoi(cstrRole);
            if (nRole < 0 || nRole > 2) {
            int nRole = _ttoi(m_gridUserManager.GetItemText(i, 3));
            if (nRole < 0 || nRole > 3) {
                CString cstrMessage;
                cstrMessage.Format(_T("用户 [%s],权限异常!将设置成操作员!"), m_gridUserManager.GetItemText(i, 1));
                AfxMessageBox(cstrMessage);
                nRole = 2;
                nRole = 3;
            }
            m_gridUserManager.SetItemText(i, 3, permissions.GetAt(nRole));
@@ -237,7 +238,8 @@
    // ç¬¬å››åˆ—设置(权限列)为下拉框
    CStringArray permissions;
    permissions.Add(_T("管理员"));
    permissions.Add(_T("工程师"));
    permissions.Add(_T("制程工程师"));
    permissions.Add(_T("设备工程师"));
    permissions.Add(_T("操作员"));
    if (pGridCtrl->SetCellType(newRowIndex, 3, RUNTIME_CLASS(CGridCellCombo))) {
@@ -443,7 +445,8 @@
        CStringArray permissions;
        permissions.Add(_T("管理员"));
        permissions.Add(_T("工程师"));
        permissions.Add(_T("制程工程师"));
        permissions.Add(_T("设备工程师"));
        permissions.Add(_T("操作员"));
        if (m_gridUserManager.SetCellType(row, 3, RUNTIME_CLASS(CGridCellCombo))) {
@@ -527,12 +530,14 @@
            if (j == 3) {
                if (cellText == _T("管理员"))
                    cellString = "0";
                else if (cellText == _T("工程师"))
                else if (cellText == _T("制程工程师"))
                    cellString = "1";
                else if (cellText == _T("设备工程师"))
                    cellString = "2";
                else if (cellText == _T("操作员"))
                    cellString = "2";
                    cellString = "3";
                else
                    cellString = "2";
                    cellString = "3";
            }
            rowData.push_back(cellString);