已添加4个文件
已修改17个文件
1078 ■■■■■ 文件已修改
SourceCode/Bond/Servo/CAttributeVector.cpp 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEFEM.cpp 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEquipment.cpp 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEquipment.h 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMaster.cpp 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Common.h 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/PageRecipe.cpp 210 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/PageRecipe.h 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/RecipeDeviceBindDlg.cpp 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/RecipeDeviceBindDlg.h 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/RecipeManager.cpp 488 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/RecipeManager.h 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.cpp 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.rc 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj.filters 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj.user 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/SystemLogManager.cpp 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/SystemLogManager.h 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/UserManager.cpp 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/resource.h 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CAttributeVector.cpp
@@ -19,9 +19,18 @@
    void CAttributeVector::addAttribute(CAttribute* pAttribute, BOOL bReplace/* = FALSE*/)
    {
        if (!pAttribute) {
            return;
        }
        const std::string& name = pAttribute->getName();
        if (name.empty()) {
            return;
        }
        if (bReplace) {
            for (auto it = m_attributes.begin(); it != m_attributes.end(); ) {
                if ((*it)->getName().compare(pAttribute->getName()) == 0) {
                if (name.compare((*it)->getName()) == 0) {
                    delete (*it);
                    it = m_attributes.erase(it);
                }
SourceCode/Bond/Servo/CEFEM.cpp
@@ -684,7 +684,7 @@
            CEqReadStep* pStep = new CEqReadStep(0x6301, 108 * 2,
                [&](void* pFrom, int code, const char* pszData, size_t size) -> int {
                    if (code == ROK && pszData != nullptr && size > 0) {
                        decodePanelDataRequest((CStep*)pFrom, pszData, size);
                        decodeFacDataReport((CStep*)pFrom, pszData, size);
                    }
                    return -1;
                });
@@ -697,19 +697,30 @@
        }
        {
            // Panel Data Request
            // JOB Data Request
            CEqReadStep* pStep = new CEqReadStep(0x617d, 2 * 2,
                [&](void* pFrom, int code, const char* pszData, size_t size) -> int {
                    if (code == ROK && pszData != nullptr && size > 0) {
                        decodePanelDataRequest((CStep*)pFrom, pszData, size);
                        decodeJobDataRequest((CStep*)pFrom, pszData, size);
                        // efme, èŽ·å–æ•°æ®åŽè¿”å›ž
                        // Cassette Sequence No        1W
                        // Job Sequence No            1W
                        // Job DataS                256W
                        char szBuffer[1024] = { 0 };
                        CJobDataS* pJobDataS = m_pPort[3]->getJobDataSWithCassette(4001, 1);
                        if (pJobDataS != nullptr) {
                            int size = pJobDataS->serialize(szBuffer, 1024);
                            ((CEqReadStep*)pFrom)->setReturnData(szBuffer, size);
                        }
                    }
                    return -1;
                });
            pStep->setName(STEP_EFEM_PANEL_DATA_REQUEST);
            pStep->setProp("Port", (void*)1);
            pStep->setWriteSignalDev(0x15d);
            pStep->setReturnDev(0x73a);
            if (addStep(STEP_ID_PANEL_DATA_REQUEST, pStep) != 0) {
            pStep->setWriteSignalDev(0x35);
            pStep->setReturnDev(0x5EA);
            if (addStep(STEP_ID_JOB_DATA_REQUEST, pStep) != 0) {
                delete pStep;
            }
        }
SourceCode/Bond/Servo/CEquipment.cpp
@@ -578,7 +578,18 @@
        // robot cmd reply
        CHECK_WRITE_STEP_SIGNAL(STEP_ID_ROBOT_CMD_REPLY, pszData, size);
        // Indexer Operation Mode Change
        CHECK_WRITE_STEP_SIGNAL(STEP_ID_IN_OP_CMD_REPLY, pszData, size);
        // Panel Data Report
        CHECK_WRITE_STEP_SIGNAL(STEP_ID_PANEL_DATA_REPORT, pszData, size);
        // Panel Data Request
        CHECK_WRITE_STEP_SIGNAL(STEP_ID_PANEL_DATA_REQUEST, pszData, size);
        // Job Data Request
        CHECK_READ_STEP_SIGNAL(STEP_ID_JOB_DATA_REQUEST, pszData, size);
    }
    BOOL CEquipment::isBitOn(const char* pszData, size_t size, int index)
@@ -1076,14 +1087,15 @@
        unsigned short operationMode = (unsigned short)((unsigned short)mode + getIndexerOperationModeBaseValue());
        LOGI("<CEquipment-%s>准备设置indexerOperationMode<%d>", m_strName.c_str(), (int)mode);
        pStep->writeShort(operationMode, [&, mode, onWritedRetBlock](int code) -> int {
        pStep->writeShort(operationMode, [&, pStep, mode, onWritedRetBlock](int code) -> int {
            int retCode = 0;
            if (code == WOK) {
                LOGI("<CEquipment-%s>设置indexerOperationMode成功.", m_strName.c_str());
                const char* pszRetData = nullptr;
                pStep->getReturnData(pszRetData);
                ASSERT(pszRetData);
                retCode = (unsigned int)CToolUnits::toInt16(pszRetData);
                retCode = (unsigned int)CToolUnits::toInt16(pszRetData);
                LOGI("<CEquipment-%s>返回值: %d", m_strName.c_str(), retCode);
            }
            else {
                LOGI("<CEquipment-%s>设置indexerOperationMode失败,code:%d", m_strName.c_str(), code);
@@ -1582,7 +1594,7 @@
        return 0;
    }
    int CEquipment::decodePanelDataRequest(CStep* pStep, const char* pszData, size_t size)
    int CEquipment::decodeJobDataRequest(CStep* pStep, const char* pszData, size_t size)
    {
        int index = 0;
        short cassetteSequenceNo, jobSequenceNo;
@@ -1590,24 +1602,9 @@
        index += sizeof(short);
        memcpy(&jobSequenceNo, &pszData[index], sizeof(short));
        index += sizeof(short);
        cassetteSequenceNo = 4000;
        jobSequenceNo = 1;
        // efme, èŽ·å–æ•°æ®åŽè¿”å›ž
        // Cassette Sequence No        1W
        // Job Sequence No            1W
        // Job DataS                256W
        char szBuffer[1024];
        index = 0;
        memcpy(&szBuffer[index], &cassetteSequenceNo, sizeof(short));
        index += sizeof(short);
        memcpy(&szBuffer[index], &jobSequenceNo, sizeof(short));
        index += sizeof(short);
        CJobDataS* pJobDataS = getJobDataSWithCassette(cassetteSequenceNo, jobSequenceNo);
        if (pJobDataS != nullptr) {
            index += pJobDataS->serialize(&szBuffer[index], 1024 - sizeof(short) - sizeof(short));
            ((CEqReadStep*)pStep)->setReturnData(szBuffer, index);
        }
        // ç¼“å­˜Attribute,用于调试时显示信息
SourceCode/Bond/Servo/CEquipment.h
@@ -223,7 +223,7 @@
        int decodeVCREventReport(CStep* pStep, const char* pszData, size_t size);
        int decodePanelDataReport(CStep* pStep, const char* pszData, size_t size);
        int decodeFacDataReport(CStep* pStep, const char* pszData, size_t size);
        int decodePanelDataRequest(CStep* pStep, const char* pszData, size_t size);
        int decodeJobDataRequest(CStep* pStep, const char* pszData, size_t size);
        BOOL compareJobData(CJobDataB* pJobDataB, CJobDataS* pJobDataS);
        void setProcessState(PROCESS_STATE state);
SourceCode/Bond/Servo/CMaster.cpp
@@ -340,7 +340,7 @@
                int nRet;
                CEquipment* pEq[6] = { pEFEM, pBonder1, pBonder2, pBakeCooling, 
                    pVacuumBake, pMeasurement};
                BOOL bIomcOk[7] = {FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE};
                BOOL bIomcOk[7] = {FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE};
                std::vector<std::promise<void>> promises(6);
                std::vector<std::future<void>> futures;
@@ -356,7 +356,7 @@
                    goto WAIT;
                }
                futures.push_back(promises[0].get_future());
                /*
                nRet = pEq[1]->indexerOperationModeChange(IDNEXER_OPERATION_MODE::Start,
                    [&](int writeCode, int retCode) -> void {
                        bIomcOk[1] = retCode == (int)RET::OK;
@@ -421,6 +421,7 @@
                    goto WAIT;
                }
                futures.push_back(promises[5].get_future());
                */
WAIT:
                for (auto& f : futures) {
@@ -450,7 +451,49 @@
            // å¤„理完成当前事务后,切换到停止或就绪状态
            else if (m_state == MASTERSTATE::STOPPING) {
                unlock();
                Sleep(1000);
                LOGI("<Master>开始切换各设备到 Stop æ¨¡å¼...");
                std::vector<std::promise<void>> promises(6);
                std::vector<std::future<void>> futures;
                BOOL bIomcOk[7] = { FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE };
                int nRet;
                CEquipment* pEq[6] = { pEFEM, pBonder1, pBonder2, pBakeCooling,
                    pVacuumBake, pMeasurement };
                for (int i = 0; i < 1; ++i) {
                    nRet = pEq[i]->indexerOperationModeChange(IDNEXER_OPERATION_MODE::Stop,
                        [i, &promises, &bIomcOk](int writeCode, int retCode) -> void {
                            bIomcOk[i] = retCode == (int)RET::OK;
                            promises[i].set_value();
                            TRACE("s000%d: ret=%d\n", i + 1, retCode);
                        });
                    if (nRet != 0) {
                        LOGI("<Master>%s切换Stop状态发送失败", pEq[i]->getName().c_str());
                        m_strLastError = pEq[i]->getName() + "切换Stop状态发送失败.";
                        bIomcOk[i] = FALSE;
                        promises[i].set_value(); // é¿å… wait é˜»å¡ž
                    }
                    futures.push_back(promises[i].get_future());
                }
                for (auto& f : futures) {
                    f.wait();  // ç­‰å¾…所有完成
                }
                for (int i = 0; i < 6; ++i) {
                    if (!bIomcOk[i]) {
                        bIomcOk[6] = FALSE;
                        LOGI("<Master>%s切换Stop状态失败", pEq[i]->getName().c_str());
                    }
                }
                if (!bIomcOk[6]) {
                    setState(MASTERSTATE::MSERROR);
                    continue;
                }
                LOGI("<Master>所有设备成功切换到 Stop æ¨¡å¼");
                setState(MASTERSTATE::READY);
                continue;
            }
SourceCode/Bond/Servo/Common.h
@@ -319,6 +319,7 @@
#define STEP_ID_FETCHED_OUT_JOB_REPORT13        0x5BB
#define STEP_ID_FETCHED_OUT_JOB_REPORT14        0x5BC
#define STEP_ID_FETCHED_OUT_JOB_REPORT15        0x5BD
#define STEP_ID_JOB_DATA_REQUEST                0x5C1
#define STEP_ID_PANEL_DATA_REQUEST                0x5D0
#define STEP_ID_PANEL_DATA_REPORT                0x5D1
#define STEP_ID_IN_OP_CMD_REPLY                    0x5F0
SourceCode/Bond/Servo/PageRecipe.cpp
@@ -5,8 +5,6 @@
#include "Servo.h"
#include "afxdialogex.h"
#include "PageRecipe.h"
#include "SECSRuntimeManager.h"
// CPageRecipe å¯¹è¯æ¡†
@@ -22,7 +20,7 @@
{
}
void CPageRecipe::FillDataToListCtrl(const std::vector<std::string>& vecData) {
void CPageRecipe::FillDataToListCtrl(const std::vector<RecipeInfo>& vecRecipe) {
    CListCtrl* pListCtrl = (CListCtrl*)GetDlgItem(IDC_LIST_PPID);
    if (pListCtrl == nullptr || pListCtrl->m_hWnd == nullptr) {
        return;
@@ -32,26 +30,23 @@
    pListCtrl->DeleteAllItems();
    // éåŽ†æ•°æ®å¹¶æ’å…¥åˆ°CListCtrl中
    for (int i = 0; i < static_cast<int>(vecData.size()); ++i) {
        // æ’入行
        pListCtrl->InsertItem(i, _T(""));
    for (int i = 0; i < static_cast<int>(vecRecipe.size()); ++i) {
        const RecipeInfo& recipe = vecRecipe[i];
        // è®¾ç½® Recipe No(第1列)
        CString strRecipeNo;
        strRecipeNo.Format(_T("%d"), i);
        pListCtrl->SetItemText(i, 1, strRecipeNo);
        m_listPPID.InsertItem(i, _T("")); // ç¬¬0列空白
        // è®¾ç½® PPID(第2列)
        CString strPPID = CA2T(vecData[i].c_str());
        if (strPPID.CompareNoCase(_T("NULL")) == 0) {
            strPPID.Empty();
        }
        pListCtrl->SetItemText(i, 2, strPPID);
        CString strNo;
        strNo.Format(_T("%d"), i + 1);
        m_listPPID.SetItemText(i, 1, strNo);
        m_listPPID.SetItemText(i, 2, CA2T(recipe.strPPID.c_str()));
        m_listPPID.SetItemText(i, 3, CA2T(recipe.strDescription.c_str()));
        m_listPPID.SetItemText(i, 4, CA2T(recipe.strCreateTime.c_str()));
    }
    // èŽ·å–åˆ—æ•°
    int nColCount = pListCtrl->GetHeaderCtrl()->GetItemCount();
    pListCtrl->SetColumnWidth(nColCount - 1, LVSCW_AUTOSIZE_USEHEADER);
    int nColCount = m_listPPID.GetHeaderCtrl()->GetItemCount();
    m_listPPID.SetColumnWidth(nColCount - 1, LVSCW_AUTOSIZE_USEHEADER);
}
void CPageRecipe::DoDataExchange(CDataExchange* pDX)
@@ -59,15 +54,16 @@
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_LIST_PPID, m_listPPID);
    DDX_Control(pDX, IDC_EDIT_PPID, m_editPPID);
    DDX_Control(pDX, IDC_EDIT_DESC, m_editDesc);
}
BEGIN_MESSAGE_MAP(CPageRecipe, CDialogEx)
    ON_WM_SIZE()
    ON_BN_CLICKED(IDC_BUTTON_SEARCH, &CPageRecipe::OnBnClickedButtonSearch)
    ON_BN_CLICKED(IDC_BUTTON_MODIFY, &CPageRecipe::OnBnClickedButtonModify)
    ON_BN_CLICKED(IDC_BUTTON_DELETE, &CPageRecipe::OnBnClickedButtonDelete)
    ON_BN_CLICKED(IDC_BUTTON_DELETE_ALL, &CPageRecipe::OnBnClickedButtonDeleteAll)
    ON_BN_CLICKED(IDC_BUTTON_SAVE, &CPageRecipe::OnBnClickedButtonSave)
    ON_BN_CLICKED(IDC_BUTTON_REFRESH, &CPageRecipe::OnBnClickedButtonRefresh)
    ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_PPID, &CPageRecipe::OnLvnItemChangedListPPID)
END_MESSAGE_MAP()
@@ -88,17 +84,82 @@
    HIMAGELIST imageList = ImageList_Create(24, 24, ILC_COLOR24, 1, 1);
    ListView_SetImageList(pListCtrl->GetSafeHwnd(), imageList, LVSIL_SMALL);
    pListCtrl->InsertColumn(0, _T(""), LVCFMT_RIGHT, 0);
    pListCtrl->InsertColumn(1, _T("Recipe No"), LVCFMT_LEFT, 100);
    pListCtrl->InsertColumn(2, _T("PPID"), LVCFMT_LEFT, 100);
    pListCtrl->SetColumnWidth(2, LVSCW_AUTOSIZE_USEHEADER);
    pListCtrl->InsertColumn(0, _T(""), LVCFMT_RIGHT, 0); // éšè—åˆ—
    pListCtrl->InsertColumn(1, _T("No."), LVCFMT_LEFT, 80);
    pListCtrl->InsertColumn(2, _T("PPID"), LVCFMT_LEFT, 120);
    pListCtrl->InsertColumn(3, _T("描述"), LVCFMT_LEFT, 180);
    pListCtrl->InsertColumn(4, _T("创建时间"), LVCFMT_LEFT, 160);
    pListCtrl->SetColumnWidth(4, LVSCW_AUTOSIZE_USEHEADER);
    // èŽ·å–æ‰€æœ‰æ•°æ®
    auto vecData = SECSRuntimeManager::getInstance().getAllPPID();
    auto vecData = RecipeManager::getInstance().getAllRecipes();
    FillDataToListCtrl(vecData);
    return TRUE;  // return TRUE unless you set the focus to a control
    // å¼‚常: OCX å±žæ€§é¡µåº”返回 FALSE
}
void CPageRecipe::OnSize(UINT nType, int cx, int cy)
{
    CDialogEx::OnSize(nType, cx, cy);
    if (m_listPPID.GetSafeHwnd()) {
        // å·¦ä¾§åˆ—表宽高自适应
        int margin = 10;
        int buttonWidth = 80;
        int buttonHeight = 30;
        int buttonSpacing = 10;
        CRect rect;
        GetClientRect(&rect);
        int listWidth = rect.Width() - buttonWidth - 3 * margin;
        int listHeight = rect.Height() - 2 * margin;
        m_listPPID.MoveWindow(margin, margin + 80, listWidth, listHeight - 80);
        // ç¼–辑框调整位置:右边对齐列表,左边固定起始
        int labelWidth = 60;
        int rightEdge = rect.right - buttonWidth - 2 * margin;
        if (m_editPPID.GetSafeHwnd()) {
            m_editPPID.MoveWindow(labelWidth, margin, rightEdge - labelWidth, 25);
        }
        if (m_editDesc.GetSafeHwnd()) {
            m_editDesc.MoveWindow(labelWidth, margin + 35, rightEdge - labelWidth, 25);
        }
        // æŒ‰é’®ç«–直排列在右侧
        CWnd* buttons[] = {
            GetDlgItem(IDC_BUTTON_SEARCH),
            GetDlgItem(IDC_BUTTON_MODIFY),
            GetDlgItem(IDC_BUTTON_DELETE),
            GetDlgItem(IDC_BUTTON_DELETE_ALL),
            GetDlgItem(IDC_BUTTON_REFRESH)
        };
        int y = margin;
        for (auto pBtn : buttons) {
            if (pBtn && pBtn->GetSafeHwnd()) {
                pBtn->MoveWindow(rect.right - buttonWidth - margin, y, buttonWidth, buttonHeight);
                y += buttonHeight + buttonSpacing;
            }
        }
        // åˆ—宽重设
        int col0 = 50;  // No.
        int col1 = 120; // PPID
        int col3 = 160; // åˆ›å»ºæ—¶é—´è‡ªåЍ填充
        int col2 = listWidth - col0 - col1 - col3 - 2;  // æè¿°è‡ªåЍ填充
        if (col2 < 80) {
            col2 = 80;
        }
        m_listPPID.SetColumnWidth(1, col0);
        m_listPPID.SetColumnWidth(2, col1);
        m_listPPID.SetColumnWidth(3, col2);
        m_listPPID.SetColumnWidth(4, col3);
    }
}
void CPageRecipe::OnBnClickedButtonSearch()
@@ -122,50 +183,114 @@
{
    // TODO: åœ¨æ­¤æ·»åŠ æŽ§ä»¶é€šçŸ¥å¤„ç†ç¨‹åºä»£ç 
    POSITION pos = m_listPPID.GetFirstSelectedItemPosition();
    if (!pos) return;
    if (!pos) {
        AfxMessageBox(_T("请选择要修改的配方"));
        return;
    }
    int nSel = m_listPPID.GetNextSelectedItem(pos);
    CString strOldPPID = m_listPPID.GetItemText(nSel, 2);
    CString strOldDesc = m_listPPID.GetItemText(nSel, 3);
    CString strNewPPID;
    CString strNewPPID, strNewDesc;
    m_editPPID.GetWindowText(strNewPPID);
    m_listPPID.SetItemText(nSel, 2, strNewPPID);
    m_editDesc.GetWindowText(strNewDesc);
    // åˆ¤ç©º
    if (strOldPPID.IsEmpty() || strNewPPID.IsEmpty()) {
        AfxMessageBox(_T("PPID ä¸èƒ½ä¸ºç©º"));
        return;
    }
    std::string oldPPID = CT2A(strOldPPID);
    std::string newPPID = CT2A(strNewPPID);
    std::string newDesc = CT2A(strNewDesc);
    bool bPPIDChanged = (strOldPPID.Compare(strNewPPID) != 0);
    bool bDescChanged = (strOldDesc.Compare(strNewDesc) != 0);
    if (!bPPIDChanged && !bDescChanged) {
        return;
    }
    if (bPPIDChanged) {
        // æ–° PPID ä¸å¯é‡å¤
        if (RecipeManager::getInstance().ppidExists(newPPID)) {
            AfxMessageBox(_T("新 PPID å·²å­˜åœ¨ï¼Œè¯·ä½¿ç”¨å…¶ä»–值"));
            return;
        }
        // è°ƒç”¨ updatePPID,同时更新描述
        if (RecipeManager::getInstance().updatePPID(oldPPID, newPPID)) {
            m_listPPID.SetItemText(nSel, 2, strNewPPID);
        }
        else {
            AfxMessageBox(_T("更新失败,请检查日志"));
        }
    }
    if (bDescChanged) {
        // æ›´æ–°æè¿°
        if (RecipeManager::getInstance().updateDescription(oldPPID, newDesc)) {
            m_listPPID.SetItemText(nSel, 3, strNewDesc);
        }
        else {
            AfxMessageBox(_T("描述更新失败"));
        }
    }
}
void CPageRecipe::OnBnClickedButtonDelete()
{
    // TODO: åœ¨æ­¤æ·»åŠ æŽ§ä»¶é€šçŸ¥å¤„ç†ç¨‹åºä»£ç 
    POSITION pos = m_listPPID.GetFirstSelectedItemPosition();
    if (!pos) return;
    if (!pos) {
        return;
    }
    int nSel = m_listPPID.GetNextSelectedItem(pos);
    m_listPPID.SetItemText(nSel, 2, _T(""));
    CString strPPID = m_listPPID.GetItemText(nSel, 2);
    std::string ppid = CT2A(strPPID);
    if (!ppid.empty()) {
        CString msg;
        msg.Format(_T("确定要删除配方 [%s] å—?"), strPPID);
        if (IDYES == AfxMessageBox(msg, MB_YESNO | MB_ICONQUESTION)) {
            if (RecipeManager::getInstance().deleteRecipeByPPID(ppid)) {
                m_listPPID.DeleteItem(nSel);
            }
            else {
                AfxMessageBox(_T("删除失败"));
            }
        }
    }
}
void CPageRecipe::OnBnClickedButtonDeleteAll()
{
    // TODO: åœ¨æ­¤æ·»åŠ æŽ§ä»¶é€šçŸ¥å¤„ç†ç¨‹åºä»£ç 
    int nCount = m_listPPID.GetItemCount();
    for (int i = 0; i < nCount; ++i) {
        m_listPPID.SetItemText(i, 2, _T(""));
    if (IDYES != AfxMessageBox(_T("确定要删除全部配方记录吗?"), MB_YESNO | MB_ICONWARNING)) {
        return;
    }
}
void CPageRecipe::OnBnClickedButtonSave()
{
    // TODO: åœ¨æ­¤æ·»åŠ æŽ§ä»¶é€šçŸ¥å¤„ç†ç¨‹åºä»£ç 
    std::vector<std::string> vecPPID;
    // èŽ·å–æ‰€æœ‰ PPID
    int nCount = m_listPPID.GetItemCount();
    for (int i = 0; i < nCount; ++i) {
        CString str = m_listPPID.GetItemText(i, 2);
        vecPPID.emplace_back(CT2A(str));
        CString strPPID = m_listPPID.GetItemText(i, 2);
        std::string ppid = CT2A(strPPID);
        if (!ppid.empty()) {
            RecipeManager::getInstance().deleteRecipeByPPID(ppid);
        }
    }
    SECSRuntimeManager::getInstance().setAllPPID(vecPPID);
    // æ¸…空列表显示
    m_listPPID.DeleteAllItems();
}
void CPageRecipe::OnBnClickedButtonRefresh()
{
    // TODO: åœ¨æ­¤æ·»åŠ æŽ§ä»¶é€šçŸ¥å¤„ç†ç¨‹åºä»£ç 
    auto vecData = SECSRuntimeManager::getInstance().getAllPPID();
    auto vecData = RecipeManager::getInstance().getAllRecipes();
    FillDataToListCtrl(vecData);
}
@@ -180,5 +305,8 @@
        int nItem = pNMLV->iItem;
        CString strPPID = m_listPPID.GetItemText(nItem, 2);
        m_editPPID.SetWindowText(strPPID);
        CString strDesc = m_listPPID.GetItemText(nItem, 3);
        m_editDesc.SetWindowText(strDesc);
    }
}
SourceCode/Bond/Servo/PageRecipe.h
@@ -1,7 +1,6 @@
#pragma once
#include "afxdialogex.h"
//#include "ListCtrlEx.h"
#include "RecipeManager.h"
// CPageRecipe å¯¹è¯æ¡†
@@ -14,7 +13,7 @@
    virtual ~CPageRecipe();
private:
    void FillDataToListCtrl(const std::vector<std::string>& vecData);
    void FillDataToListCtrl(const std::vector<RecipeInfo>& vecRecipe);
// å¯¹è¯æ¡†æ•°æ®
#ifdef AFX_DESIGN_TIME
@@ -24,11 +23,11 @@
protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV æ”¯æŒ
    virtual BOOL OnInitDialog();
    afx_msg void OnSize(UINT nType, int cx, int cy);
    afx_msg void OnBnClickedButtonSearch();
    afx_msg void OnBnClickedButtonModify();
    afx_msg void OnBnClickedButtonDelete();
    afx_msg void OnBnClickedButtonDeleteAll();
    afx_msg void OnBnClickedButtonSave();
    afx_msg void OnBnClickedButtonRefresh();
    afx_msg void OnLvnItemChangedListPPID(NMHDR* pNMHDR, LRESULT* pResult);
    DECLARE_MESSAGE_MAP()
@@ -36,4 +35,5 @@
private:
    CListCtrl m_listPPID;
    CEdit m_editPPID;
    CEdit m_editDesc;
};
SourceCode/Bond/Servo/RecipeDeviceBindDlg.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
// RecipeDeviceBindDlg.cpp: å®žçŽ°æ–‡ä»¶
//
#include "stdafx.h"
#include "Servo.h"
#include "afxdialogex.h"
#include "RecipeDeviceBindDlg.h"
// CRecipeDeviceBindDlg å¯¹è¯æ¡†
IMPLEMENT_DYNAMIC(CRecipeDeviceBindDlg, CDialogEx)
CRecipeDeviceBindDlg::CRecipeDeviceBindDlg(CWnd* pParent /*=nullptr*/)
    : CDialogEx(IDD_DIALOG_RECIPE_DEVICE_BIND, pParent)
{
}
CRecipeDeviceBindDlg::~CRecipeDeviceBindDlg()
{
}
void CRecipeDeviceBindDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CRecipeDeviceBindDlg, CDialogEx)
END_MESSAGE_MAP()
// CRecipeDeviceBindDlg æ¶ˆæ¯å¤„理程序
SourceCode/Bond/Servo/RecipeDeviceBindDlg.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
#pragma once
#include "afxdialogex.h"
// CRecipeDeviceBindDlg å¯¹è¯æ¡†
class CRecipeDeviceBindDlg : public CDialogEx
{
    DECLARE_DYNAMIC(CRecipeDeviceBindDlg)
public:
    CRecipeDeviceBindDlg(CWnd* pParent = nullptr);   // æ ‡å‡†æž„造函数
    virtual ~CRecipeDeviceBindDlg();
// å¯¹è¯æ¡†æ•°æ®
#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_DIALOG_RECIPE_DEVICE_BIND };
#endif
protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV æ”¯æŒ
    DECLARE_MESSAGE_MAP()
};
SourceCode/Bond/Servo/RecipeManager.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,488 @@
#include "stdafx.h"
#include "RecipeManager.h"
#include <sstream>
#include <iomanip>
#include <fstream>
#include <iostream>
std::recursive_mutex RecipeManager::m_mutex;
RecipeManager& RecipeManager::getInstance() {
    static RecipeManager instance;
    return instance;
}
RecipeManager::RecipeManager() {
    m_pDB = new BL::SQLiteDatabase();
}
RecipeManager::~RecipeManager() {
    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);
    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;
    }
    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 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)
        );
    )";
    return m_pDB->executeQuery(createRecipeTable)
        && m_pDB->executeQuery(createDeviceTable);
}
void RecipeManager::termRecipeTable() {
    if (!m_pDB) {
        return;
    }
    m_pDB->disconnect();
}
bool RecipeManager::destroyRecipeTable() {
    if (!m_pDB) {
        return false;
    }
    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");
}
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");
}
bool RecipeManager::addRecipe(const RecipeInfo& recipe) {
    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::lock_guard<std::recursive_mutex> lock(m_mutex);
    // å¼€å§‹äº‹åŠ¡
    m_pDB->executeQuery("BEGIN TRANSACTION;");
    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;
    }
    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 << ");";
        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;
    }
    // æ£€æŸ¥ 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::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;
    }
    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());
}
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;
    }
    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::vector<RecipeInfo> RecipeManager::getAllRecipes() {
    if (!m_pDB) {
        return {};
    }
    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];
        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());
        for (const auto& dev : devs) {
            DeviceRecipe dr;
            dr.strPPID = info.strPPID;
            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);
    }
    return recipes;
}
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;
    }
    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;
        dr.strPPID = ppid;
        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;
}
int RecipeManager::getDeviceRecipeIDByID(const std::string& ppid, int nDeviceID) {
    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 << ";";
    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;
    }
    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;
}
bool RecipeManager::deleteRecipeByPPID(const std::string& ppid) {
    if (!m_pDB) {
        return false;
    }
    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 (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);
}
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;
    }
    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;
    }
    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;
    }
    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;
    }
    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());
}
bool RecipeManager::updateDeviceRecipeIDByID(const std::string& ppid, int nDeviceID, int nNewRecipeID) {
    if (!m_pDB || ppid.empty() || nDeviceID <= 0 || nNewRecipeID <= 0) {
        return false;
    }
    std::ostringstream query;
    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());
}
bool RecipeManager::updateDeviceRecipeIDByName(const std::string& ppid, const std::string& strDeviceName, int nNewRecipeID) {
    if (!m_pDB || ppid.empty() || strDeviceName.empty() || nNewRecipeID <= 0) {
        return false;
    }
    std::ostringstream query;
    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());
}
void RecipeManager::insertMockData() {
    if (!m_pDB) {
        return;
    }
    RecipeInfo recipe;
    recipe.strPPID = "P1001";
    recipe.strDescription = "Main Board Burn-in";
    recipe.vecDeviceList = {
        {1, 101, "P1001","Burner A"},
        {2, 102, "P1001", "Burner B"}
    };
    addRecipe(recipe);
}
bool RecipeManager::readRecipeFile(const std::string& filename) {
    if (!m_pDB) {
        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
    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, ',');
        dev.strPPID = ppid;
        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;
        }
    }
    return true;
}
bool RecipeManager::saveRecipeFile(const std::string& filename) {
    if (!m_pDB) {
        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";
        }
    }
    return true;
}
SourceCode/Bond/Servo/RecipeManager.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,109 @@
#ifndef RECIPE_MANAGER_H
#define RECIPE_MANAGER_H
#include <string>
#include <vector>
#include <mutex>
#include <unordered_map>
#include "Database.h"
// å•个设备配方映射信息
struct DeviceRecipe {
    int nDeviceID;               // è®¾å¤‡ID
    int nRecipeID;               // è¯¥è®¾å¤‡å¯¹åº”的子配方ID
    std::string strPPID;         // é…æ–¹ID(主键)
    std::string strDeviceName;   // è®¾å¤‡åç§°
};
// é…æ–¹ä¿¡æ¯
struct RecipeInfo {
    std::string strPPID;         // é…æ–¹ID
    std::string strDescription;  // é…æ–¹æè¿°
    std::string strCreateTime;   // åˆ›å»ºæ—¶é—´
    std::vector<DeviceRecipe> vecDeviceList;  // å…³è”的设备信息列表
};
using RecipeMap = std::unordered_map<std::string, RecipeInfo>; // æŒ‰ PPID æ˜ å°„的配方表
class RecipeManager {
public:
    // èŽ·å–å•ä¾‹
    static RecipeManager& getInstance();
    // åˆå§‹åŒ–配方数据库
    bool initRecipeTable();
    // é”€æ¯è¡¨æˆ–关闭连接
    void termRecipeTable();
    bool destroyRecipeTable();
    // æ£€æŸ¥ PPID æ˜¯å¦å­˜åœ¨
    bool ppidExists(const std::string& ppid);
    // æ£€æŸ¥è®¾å¤‡æ˜¯å¦å­˜åœ¨äºŽæŒ‡å®š PPID çš„配方中
    bool deviceExists(const std::string& ppid, int nDeviceID);
    // æ·»åŠ ä¸€ä¸ªé…æ–¹åŠå…¶è®¾å¤‡æ˜ å°„
    bool addRecipe(const RecipeInfo& recipe);
    // æ·»åŠ è®¾å¤‡åˆ°æŒ‡å®šé…æ–¹
    bool addRecipeDevice(const std::string& ppid, const DeviceRecipe& device);
    // åˆ é™¤æŒ‡å®š PPID çš„设备配方
    bool deleteRecipeDeviceByID(const std::string& ppid, int nDeviceID);
    // åˆ é™¤æŒ‡å®š PPID çš„设备配方(通过设备名称)
    bool deleteRecipeDeviceByName(const std::string& ppid, const std::string& strDeviceName);
    // æŸ¥è¯¢æ‰€æœ‰é…æ–¹
    std::vector<RecipeInfo> getAllRecipes();
    // æŒ‰ PPID æŸ¥è¯¢é…æ–¹
    RecipeInfo getRecipeByPPID(const std::string& ppid);
    // æ ¹æ® PPID å’Œè®¾å¤‡ID èŽ·å–è®¾å¤‡é…æ–¹ID
    int getDeviceRecipeIDByID(const std::string& ppid, int nDeviceID);
    // æ ¹æ® PPID å’Œè®¾å¤‡åç§° èŽ·å–è®¾å¤‡é…æ–¹ID
    int getDeviceRecipeIDByName(const std::string& ppid, const std::string& strDeviceName);
    // åˆ é™¤æŒ‡å®š PPID çš„配方
    bool deleteRecipeByPPID(const std::string& ppid);
    // æ›´æ–°æŒ‡å®š PPID çš„配方
    bool updateRecipe(const RecipeInfo& recipe);
    // æ›´æ–° PPID(通过旧 PPID å’Œæ–° PPID)
    bool updatePPID(const std::string& oldPPID, const std::string& newPPID);
    // æ›´æ–°é…æ–¹æè¿°ï¼ˆé€šè¿‡ PPID)
    bool updateDescription(const std::string& ppid, const std::string& newDescription);
    // æ›´æ–°è®¾å¤‡é…æ–¹ID(通过 PPID å’Œè®¾å¤‡ID)
    bool updateDeviceRecipeIDByID(const std::string& ppid, int nDeviceID, int nNewRecipeID);
    // æ›´æ–°è®¾å¤‡é…æ–¹ID(通过 PPID å’Œè®¾å¤‡åç§°ï¼‰
    bool updateDeviceRecipeIDByName(const std::string& ppid, const std::string& strDeviceName, int nNewRecipeID);
    // æ¨¡æ‹Ÿæ’入数据(测试用)
    void insertMockData();
    // è¯»å–配方文件(CSV æˆ– JSON)
    bool readRecipeFile(const std::string& filename);
    // ä¿å­˜é…æ–¹åˆ°æ–‡ä»¶
    bool saveRecipeFile(const std::string& filename);
private:
    RecipeManager();
    ~RecipeManager();
    RecipeManager(const RecipeManager&) = delete;
    RecipeManager& operator=(const RecipeManager&) = delete;
private:
    BL::Database* m_pDB;
    static std::recursive_mutex m_mutex;
};
#endif // RECIPE_MANAGER_H
SourceCode/Bond/Servo/Servo.cpp
@@ -7,10 +7,10 @@
#include "ServoDlg.h"
#include "ServoGraph.h"
#include "AlarmManager.h"
#include "SECSRuntimeManager.h"
#include "TransferManager.h"
#include "SystemLogManager.h"
#include "UserManager.h"
#include "RecipeManager.h"
#include "VerticalLine.h"
#include "HorizontalLine.h"
#include "EqsGraphWnd.h"
@@ -134,21 +134,6 @@
        AfxMessageBox(errorMsg, MB_ICONERROR);
        return FALSE;
    }
    AlarmManager::getInstance().insertMockData();
    // åˆå§‹åŒ–SECS运行设置管理库
    try {
        if (!SECSRuntimeManager::getInstance().initRuntimeSetting()) {
            AfxMessageBox("初始化SECS运行设置失败!");
            return FALSE;
        }
    }
    catch (const std::exception& ex) {
        CString errorMsg;
        errorMsg.Format(_T("初始化SECS运行设置失败:%s"), CString(ex.what()));
        AfxMessageBox(errorMsg, MB_ICONERROR);
        return FALSE;
    }
    // åˆå§‹åŒ–搬运记录管理库
    try {
@@ -166,7 +151,7 @@
    // åˆå§‹åŒ–运行日志管理库
    try {
        if (!SystemLogManager::getInstance().initializeLogTable()) {
        if (!SystemLogManager::getInstance().initSystemLogTable()) {
            AfxMessageBox("初始化运行日志管理库失败!");
            return FALSE;
        }
@@ -194,6 +179,19 @@
        return FALSE;
    }
    // åˆå§‹åŒ–配方管理库
    try {
        if (!RecipeManager::getInstance().initRecipeTable()) {
            AfxMessageBox("初始化配方管理库失败!");
            return FALSE;
        }
    }
    catch (const std::exception& ex) {
        CString errorMsg;
        errorMsg.Format(_T("初始化配方管理库失败:%s"), CString(ex.what()));
        AfxMessageBox(errorMsg, MB_ICONERROR);
        return FALSE;
    }
    CServoDlg dlg;
@@ -238,12 +236,22 @@
    // é”€æ¯æŠ¥è­¦è¡¨
    AlarmManager::getInstance().termAlarmTable();
    // é”€æ¯SECS运行设置管理库
    SECSRuntimeManager::getInstance().termRuntimeSetting();
    // é”€æ¯æ¬è¿è®°å½•管理库
    TransferManager::getInstance().termTransferTable();
    // é”€æ¯è¿è¡Œæ—¥å¿—
    SystemLogManager::getInstance().termSystemLogTable();
    // é”€æ¯ç”¨æˆ·è¡¨
#if !defined(_DEBUG)
// æ¸…除 UserManager çš„æ— æ“ä½œæ£€æµ‹
    UserManager::getInstance().terminateIdleDetection();
    KillTimer(1);
#endif
    // é”€æ¯é…æ–¹è¡¨
    RecipeManager::getInstance().termRecipeTable();
    return CWinApp::ExitInstance();
}
SourceCode/Bond/Servo/Servo.rc
Binary files differ
SourceCode/Bond/Servo/Servo.vcxproj
@@ -317,6 +317,8 @@
    <ClInclude Include="PageTransferLog.h" />
    <ClInclude Include="PortConfigurationDlg.h" />
    <ClInclude Include="ProductionLogManager.h" />
    <ClInclude Include="RecipeDeviceBindDlg.h" />
    <ClInclude Include="RecipeManager.h" />
    <ClInclude Include="Resource.h" />
    <ClInclude Include="SECSRuntimeManager.h" />
    <ClInclude Include="SecsTestDlg.h" />
@@ -454,6 +456,8 @@
    <ClCompile Include="PageTransferLog.cpp" />
    <ClCompile Include="PortConfigurationDlg.cpp" />
    <ClCompile Include="ProductionLogManager.cpp" />
    <ClCompile Include="RecipeDeviceBindDlg.cpp" />
    <ClCompile Include="RecipeManager.cpp" />
    <ClCompile Include="SECSRuntimeManager.cpp" />
    <ClCompile Include="SecsTestDlg.cpp" />
    <ClCompile Include="Servo.cpp" />
SourceCode/Bond/Servo/Servo.vcxproj.filters
@@ -167,6 +167,8 @@
    <ClCompile Include="SystemLogManager.cpp" />
    <ClCompile Include="UserManager.cpp" />
    <ClCompile Include="InputDialog.cpp" />
    <ClCompile Include="RecipeManager.cpp" />
    <ClCompile Include="RecipeDeviceBindDlg.cpp" />
  </ItemGroup>
  <ItemGroup>
    <ClInclude Include="AlarmManager.h" />
@@ -339,6 +341,8 @@
    <ClInclude Include="UserManager.h" />
    <ClInclude Include="SystemLogManager.h" />
    <ClInclude Include="InputDialog.h" />
    <ClInclude Include="RecipeManager.h" />
    <ClInclude Include="RecipeDeviceBindDlg.h" />
  </ItemGroup>
  <ItemGroup>
    <ResourceCompile Include="Servo.rc" />
SourceCode/Bond/Servo/Servo.vcxproj.user
@@ -7,6 +7,6 @@
    <RemoteDebuggerCommand>\\DESKTOP-IODBVIQ\Servo\Debug\Servo.exe</RemoteDebuggerCommand>
    <RemoteDebuggerWorkingDirectory>\\DESKTOP-IODBVIQ\Servo\Debug\</RemoteDebuggerWorkingDirectory>
    <RemoteDebuggerServerName>DESKTOP-IODBVIQ</RemoteDebuggerServerName>
    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
    <DebuggerFlavor>WindowsRemoteDebugger</DebuggerFlavor>
  </PropertyGroup>
</Project>
SourceCode/Bond/Servo/SystemLogManager.cpp
@@ -29,7 +29,7 @@
}
// åˆå§‹åŒ–日志表
bool SystemLogManager::initializeLogTable() {
bool SystemLogManager::initSystemLogTable() {
    // èŽ·å–å¯æ‰§è¡Œæ–‡ä»¶è·¯å¾„
    char szPath[MAX_PATH];
    GetModuleFileName(NULL, szPath, MAX_PATH);
@@ -42,7 +42,7 @@
    }
    // æž„造数据库路径
    std::string dbPath = dbDir + "\\SystemLog.db";
    std::string dbPath = dbDir + "\\SystemLogManager.db";
    // è¿žæŽ¥æ•°æ®åº“
    if (!m_pDB->connect(dbPath, true)) {
@@ -63,6 +63,15 @@
    return m_pDB->executeQuery(createTableQuery);
}
// ç»ˆæ­¢æ•°æ®åº“连接
void SystemLogManager::termSystemLogTable() {
    if (!m_pDB) {
        return;
    }
    m_pDB->disconnect();
}
// æ·»åŠ æ—¥å¿—ï¼ˆä½¿ç”¨å½“å‰ç”¨æˆ·ï¼‰
bool SystemLogManager::log(LogType logType, const std::string& event) {
    if (!m_pDB) {
SourceCode/Bond/Servo/SystemLogManager.h
@@ -21,7 +21,10 @@
    static SystemLogManager& getInstance();
    // åˆå§‹åŒ–日志表
    bool initializeLogTable();
    bool initSystemLogTable();
    // ç»ˆæ­¢æ•°æ®åº“连接
    void termSystemLogTable();
    // æ·»åŠ æ—¥å¿—
    bool log(LogType logType, const std::string& event);
SourceCode/Bond/Servo/UserManager.cpp
@@ -7,7 +7,7 @@
#include <sstream>
const std::string SESSION_FILE = R"(session.dat)";
const std::string DATABASE_FILE = R"(BondEq.db)";
const std::string DATABASE_FILE = R"(UserManager.db)";
const std::string INITIAL_ADMIN_USERNAME = "admin";
const std::string INITIAL_ADMIN_PASSWORD = "admin";
@@ -160,7 +160,7 @@
    char szPath[MAX_PATH];
    GetModuleFileName(NULL, szPath, MAX_PATH);
    std::string exePath(szPath);
    std::string dbDir = exePath.substr(0, exePath.find_last_of("\\/")) + "\\DB";
    std::string dbDir = exePath.substr(0, exePath.find_last_of("\\/")) + "\\DB\\";
    // æ£€æŸ¥å¹¶åˆ›å»ºconfig文件夹
    DWORD fileAttr = GetFileAttributes(dbDir.c_str());
SourceCode/Bond/Servo/resource.h
Binary files differ