已添加5个文件
已修改22个文件
1225 ■■■■■ 文件已修改
Document/Panel Bonder八零联合 SecsTest CheckList_v3.0.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CLoadPort.cpp 101 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CLoadPort.h 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMaster.cpp 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMyStatusbar.cpp 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMyStatusbar.h 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPanelMaster.cpp 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPanelMaster.h 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPanelProduction.cpp 198 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPanelProduction.h 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Common.h 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Configuration.h 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ConfigurationProduction.cpp 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/HsmsPassive.cpp 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/HsmsPassive.h 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Model.cpp 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ProductionStats.cpp 377 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ProductionStats.h 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.rc 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj.filters 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ServoDlg.cpp 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ServoDlg.h 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/resource.h 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/x64/Debug/CollectionEventList.txt 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/x64/Debug/ReportList.txt 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/x64/Debug/VariableList.txt 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Document/Panel Bonder°ËÁãÁªºÏ SecsTest CheckList_v3.0.xlsx
Binary files differ
SourceCode/Bond/Servo/CLoadPort.cpp
@@ -1,4 +1,4 @@
#include "stdafx.h"
#include "stdafx.h"
#include "CLoadPort.h"
#include "CGlassPool.h"
#include "Servo.h"
@@ -17,6 +17,7 @@
        m_bAutoChangeEnable = FALSE;
        m_nNextCassetteSequenceNo = 0;
        m_isCompareMapsBeforeProceeding = FALSE;
        m_downloadCassetteMap = 0;
    }
    CLoadPort::~CLoadPort()
@@ -40,16 +41,16 @@
        CEquipment::term();
    }
    // å¿…须要实现的虚函数,在此初始化Pin列表
    // å¿…须要实现的虚函数,在此初始化Pin列表
    void CLoadPort::initPins()
    {
        // åŠ å…¥Pin初始化代码
        // åŠ å…¥Pin初始化代码
        LOGD("<CLoadPort>initPins");
        addPin(SERVO::PinType::INPUT, _T("In"));
        addPin(SERVO::PinType::OUTPUT, _T("Out"));
    }
    // å¿…须要实现的虚函数,在此初始化Slot信息
    // å¿…须要实现的虚函数,在此初始化Slot信息
    void CLoadPort::initSlots()
    {
        for (int i = 0; i < SLOT_MAX; i++) {
@@ -351,7 +352,7 @@
        CEquipment::onTimer(nTimerid);
        // ä»Žé…ç½®è¯»å‡ºçš„enable,初始化时写给efem
        // ä»Žé…ç½®è¯»å‡ºçš„enable,初始化时写给efem
        static int i_enable[4] = { 0 };
        if ((++i_enable[m_nIndex]) == 20 + m_nIndex) {
            eablePort(m_bEnable, [&](int code) -> int {
@@ -362,7 +363,7 @@
        // æ¨¡æ‹Ÿæµ‹è¯•
        // æ¨¡æ‹Ÿæµ‹è¯•
        /*
        if (m_nIndex == 0) {
            static int ii = 0;
@@ -934,27 +935,15 @@
        m_portStatusReport.copyEx(portStatusReport);
        // å½“port状态为InUse, æ¯”较map
        // å½“port状态为InUse, æ¯”较map
        if (m_portStatusReport.getPortStatus() == PORT_INUSE) {
            if (m_isCompareMapsBeforeProceeding) {
                short scanMap = getScanCassetteMap();
                short downloadMap = getDownloadCassetteMap();
                if (scanMap == downloadMap) {
                    generateGlassList(scanMap);
                    this->sendCassetteCtrlCmd(CCC_PROCESS_START, nullptr, 0, 0, 0, nullptr, nullptr);
                }
                else {
                    this->sendCassetteCtrlCmd(CCC_PROCESS_CANCEL, nullptr, 0, 0, 0, nullptr, nullptr);
            // ç”ŸæˆçŽ»ç’ƒåˆ—è¡¨ï¼šæ¥è‡ª EFEM æ‰«æåˆ°çš„ map
            generateGlassList(getScanCassetteMap());
                    // æŠ›å‡ºåˆ°åº”用层做提示
                    if (m_listener.onMapMismatch != nullptr) {
                        m_listener.onMapMismatch(this, scanMap, downloadMap);
                    }
                }
            }
            else {
                // æŠ›å‡ºåˆ°åº”用层做选择要加工的片子
                generateGlassList(getScanCassetteMap());
            // CompareMapsBeforeProceeding:不在此处自动 Start/Cancel,改为等待 Host å†³ç­–(ProceedWithCarrier/ProceedWithSlotMap/CarrierRelease)
            // Host å†³ç­–入口:S3F17 CarrierAction -> listener.onCarrierAction -> CMaster::proceedWithCarrier()/carrierRelease()
            if (m_isCompareMapsBeforeProceeding) {
                // è¿™é‡Œä»…等待,具体上报由上层在 PORT_INUSE äº‹ä»¶ä¸­è§¦å‘(S6F11 CheckSlotMap)
            }
        }
        if (m_listener.onPortStatusChanged != nullptr) {
@@ -963,7 +952,7 @@
        }
        // ç¼“å­˜Attribute,用于调试时显示信息
        // ç¼“å­˜Attribute,用于调试时显示信息
        unsigned int weight = 201;
        CAttributeVector& attrubutes = pStep->attributeVector();
        m_portStatusReport.getAttributeVector(attrubutes, weight);
@@ -988,17 +977,17 @@
            return -1;
        }
        LOGI("<CLoadPort-%d>准备设置Port type<%d>", m_nIndex, (int)type);
        LOGI("<CLoadPort-%d>准备设置Port type<%d>", m_nIndex, (int)type);
        short value = (short)type;
        pStep->writeDataEx((const char*)&value, sizeof(short), [&, onWritedBlock](int code) -> int {
            // test
            code = WOK;
            if (code == WOK) {
                m_portType = type;
                LOGI("<CLoadPort-%d>设置Port type成功.", m_nIndex);
                LOGI("<CLoadPort-%d>设置Port type成功.", m_nIndex);
            }
            else {
                LOGE("<CLoadPort-%d>设置Port type失败,code:%d", m_nIndex, code);
                LOGE("<CLoadPort-%d>设置Port type失败,code:%d", m_nIndex, code);
            }
            if (onWritedBlock != nullptr) {
                return onWritedBlock(code);
@@ -1018,17 +1007,17 @@
            return -1;
        }
        LOGI("<CLoadPort-%d>准备%s Port", m_nIndex, bEnable ? _T("启用") : _T("禁用"));
        LOGI("<CLoadPort-%d>准备%s Port", m_nIndex, bEnable ? _T("启用") : _T("禁用"));
        short value = bEnable ? 1 : 2;
        pStep->writeDataEx((const char*)&value, sizeof(short), [&, onWritedBlock](int code) -> int {
            // test
            code = WOK;
            if (code == WOK) {
                m_bEnable = bEnable;
                LOGI("<CLoadPort-%d>%s Port成功.", m_nIndex, bEnable ? _T("启用") : _T("禁用"));
                LOGI("<CLoadPort-%d>%s Port成功.", m_nIndex, bEnable ? _T("启用") : _T("禁用"));
            }
            else {
                LOGE("<CLoadPort-%d>%s  Port失败,code:%d", m_nIndex, bEnable ? _T("启用") : _T("禁用"), code);
                LOGE("<CLoadPort-%d>%s  Port失败,code:%d", m_nIndex, bEnable ? _T("启用") : _T("禁用"), code);
            }
            if (onWritedBlock != nullptr) {
                return onWritedBlock(code);
@@ -1047,17 +1036,17 @@
            return -1;
        }
        LOGI("<CLoadPort-%d>准备设置Port mode<%d>", m_nIndex, (int)mode);
        LOGI("<CLoadPort-%d>准备设置Port mode<%d>", m_nIndex, (int)mode);
        short value = (short)mode;
        pStep->writeDataEx((const char*)&value, sizeof(short), [&, onWritedBlock](int code) -> int {
            // test
            code = WOK;
            if (code == WOK) {
                m_portMode = mode;
                LOGI("<CLoadPort-%d>设置Port mode成功.", m_nIndex);
                LOGI("<CLoadPort-%d>设置Port mode成功.", m_nIndex);
            }
            else {
                LOGE("<CLoadPort-%d>设置Port mode失败,code:%d", m_nIndex, code);
                LOGE("<CLoadPort-%d>设置Port mode失败,code:%d", m_nIndex, code);
            }
            if (onWritedBlock != nullptr) {
                return onWritedBlock(code);
@@ -1077,16 +1066,16 @@
            return -1;
        }
        LOGI("<CLoadPort-%d>准备设置Cassette Type<%d>", m_nIndex, (int)type);
        LOGI("<CLoadPort-%d>准备设置Cassette Type<%d>", m_nIndex, (int)type);
        short value = (short)type;
        pStep->writeDataEx((const char*)&value, sizeof(short), [&, onWritedBlock](int code) -> int {
            // test
            code = WOK;
            if (code == WOK) {
                LOGI("<CLoadPort-%d>设置Cassette Type成功.", m_nIndex);
                LOGI("<CLoadPort-%d>设置Cassette Type成功.", m_nIndex);
            }
            else {
                LOGE("<CLoadPort-%d>设置Cassette Type失败,code:%d", m_nIndex, code);
                LOGE("<CLoadPort-%d>设置Cassette Type失败,code:%d", m_nIndex, code);
            }
            if (onWritedBlock != nullptr) {
                return onWritedBlock(code);
@@ -1105,17 +1094,17 @@
            return -1;
        }
        LOGI("<CLoadPort-%d>准备设置Transfer mode<%d>", m_nIndex, (int)mode);
        LOGI("<CLoadPort-%d>准备设置Transfer mode<%d>", m_nIndex, (int)mode);
        short value = (short)mode;
        pStep->writeDataEx((const char*)&value, sizeof(short), [&, onWritedBlock](int code) -> int {
            // test
            code = WOK;
            if (code == WOK) {
                m_transferMode = mode;
                LOGI("<CLoadPort-%d>设置Transfer mode成功.", m_nIndex + 1);
                LOGI("<CLoadPort-%d>设置Transfer mode成功.", m_nIndex + 1);
            }
            else {
                LOGE("<CLoadPort-%d>设置Transfer mode失败,code:%d", m_nIndex + 1, code);
                LOGE("<CLoadPort-%d>设置Transfer mode失败,code:%d", m_nIndex + 1, code);
            }
            if (onWritedBlock != nullptr) {
                return onWritedBlock(code);
@@ -1134,17 +1123,17 @@
            return -1;
        }
        LOGI("<CLoadPort-%d>准备%s Auto Change", m_nIndex, bEnable ? _T("启用") : _T("禁用"));
        LOGI("<CLoadPort-%d>准备%s Auto Change", m_nIndex, bEnable ? _T("启用") : _T("禁用"));
        short value = bEnable ? 1 : 2;
        pStep->writeDataEx((const char*)&value, sizeof(short), [&, onWritedBlock](int code) -> int {
            // test
            code = WOK;
            if (code == WOK) {
                m_bAutoChangeEnable = bEnable;
                LOGI("<CLoadPort-%d>%s Auto Change成功.", m_nIndex, bEnable ? _T("启用") : _T("禁用"));
                LOGI("<CLoadPort-%d>%s Auto Change成功.", m_nIndex, bEnable ? _T("启用") : _T("禁用"));
            }
            else {
                LOGE("<CLoadPort-%d>%s  Auto Change失败,code:%d", m_nIndex, bEnable ? _T("启用") : _T("禁用"), code);
                LOGE("<CLoadPort-%d>%s  Auto Change失败,code:%d", m_nIndex, bEnable ? _T("启用") : _T("禁用"), code);
            }
            if (onWritedBlock != nullptr) {
                return onWritedBlock(code);
@@ -1192,17 +1181,20 @@
    short CLoadPort::getDownloadCassetteMap()
    {
        // æš‚时未实现此功能
        short map = 0;
        return map;
        return m_downloadCassetteMap;
    }
    void CLoadPort::setDownloadCassetteMap(short map)
    {
        m_downloadCassetteMap = map;
    }
    /*
     * ç”Ÿæˆæµ‹è¯•用的玻璃列表
     * ç”Ÿæˆæµ‹è¯•用的玻璃列表
     */
    int CLoadPort::testGenerateGlassList(MaterialsType type)
    {
        // å¦‚果非空就不生成了
        // å¦‚果非空就不生成了
        Lock();
        if (hasGlass()) {
            Unlock();
@@ -1236,11 +1228,11 @@
    }
    /*
     * æ ¹æ®efem扫描到的map,生成玻璃列表
     * æ ¹æ®efem扫描到的map,生成玻璃列表
     */
    int CLoadPort::generateGlassList(short map)
    {
        // å…ˆé‡Šæ”¾è¾ƒæ—©å‰çš„æ•°æ®
        // å…ˆé‡Šæ”¾è¾ƒæ—©å‰çš„æ•°æ®
        Lock();
        for (int i = 0; i < SLOT_MAX; i++) {
            m_slot[i].setContext(nullptr);
@@ -1248,7 +1240,7 @@
        Unlock();
        // æ ¹æ®map生成新的
        // æ ¹æ®map生成新的
        char szBuffer[64];
        for (int i = 0; i < SLOT_MAX; i++) {
            if (!m_slot[i].isEnable()) continue;
@@ -1320,4 +1312,9 @@
    {
        m_isCompareMapsBeforeProceeding = bCompare;
    }
    BOOL CLoadPort::isCompareMapsBeforeProceeding() const
    {
        return m_isCompareMapsBeforeProceeding;
    }
}
SourceCode/Bond/Servo/CLoadPort.h
@@ -1,4 +1,4 @@
#pragma once
#pragma once
#include "CEquipment.h"
#include "ServoCommo.h"
@@ -39,6 +39,7 @@
        void localAutoChangeEnable(BOOL bEnable);
        short getScanCassetteMap();
        short getDownloadCassetteMap();
        void setDownloadCassetteMap(short map);
    public:
        short getNextCassetteSequenceNo();
@@ -85,6 +86,7 @@
            ONWRITED onWritedBlock);
        CStep* getCassetteCtrlCmdStep();
        void setCompareMapsBeforeProceeding(BOOL bCompare);
        BOOL isCompareMapsBeforeProceeding() const;
    private:
        int decodePortStatusReport(CStep* pStep, const char* pszData, size_t size);
@@ -100,8 +102,8 @@
        CPortStatusReport m_portStatusReport;
        int m_nNextCassetteSequenceNo;
        // åœ¨å¼€å§‹å·¥è‰ºå‰æ˜¯å¦å…ˆéœ€è¦å…ˆæ¯”较map
        // åœ¨å¼€å§‹å·¥è‰ºå‰æ˜¯å¦å…ˆéœ€è¦å…ˆæ¯”较map
        BOOL m_isCompareMapsBeforeProceeding;
        short m_downloadCassetteMap;
    };
}
SourceCode/Bond/Servo/CMaster.cpp
@@ -2601,15 +2601,31 @@
        }
        m_processJobs = temp;
        // é‡ç½®å„端口 DownloadMap(Host/本地勾选的期望加工槽位)
        for (int i = 0; i < 4; i++) {
            auto* pPort = (CLoadPort*)getEquipment(EQ_ID_LOADPORT1 + i);
            if (pPort != nullptr) {
                pPort->setDownloadCassetteMap(0);
            }
        }
        // æ›´æ–°context
        std::vector<uint8_t> newSlots;
        std::vector<void*> newContexts;
        for (auto pj : m_processJobs) {
            for (auto& c : pj->carriers()) {
                auto pPort = getPortWithCarrierId(c.carrierId);
                if (pPort == nullptr) continue;
                short downloadMap = 0;
                for (auto s : c.slots) {
                    if (s >= 1 && s <= 8) {
                        downloadMap |= (short)(1 << (s - 1));
                    }
                }
                pPort->setDownloadCassetteMap((short)(pPort->getDownloadCassetteMap() | downloadMap));
                std::vector<uint8_t> newSlots;
                std::vector<void*> newContexts;
                for (auto s : c.slots) {
                    auto pGlass = pPort->getGlassFromSlot(s);
                    if (pGlass == nullptr) continue;
SourceCode/Bond/Servo/CMyStatusbar.cpp
@@ -80,6 +80,24 @@
    SetDlgItemText(IDC_BUTTON_CIM, pszText);
}
void CMyStatusbar::setCurTaskBtnColors(COLORREF face, COLORREF frame, COLORREF text)
{
    m_btnCurTask.SetFaceColor(face);
    m_btnCurTask.SetFrameColor(frame);
    m_btnCurTask.SetTextColor(text);
    Invalidate();
    UpdateWindow();
}
void CMyStatusbar::setCimBtnColors(COLORREF face, COLORREF frame, COLORREF text)
{
    m_btnCim.SetFaceColor(face);
    m_btnCim.SetFrameColor(frame);
    m_btnCim.SetTextColor(text);
    Invalidate();
    UpdateWindow();
}
BOOL CMyStatusbar::OnInitDialog()
{
    CDialogEx::OnInitDialog();
@@ -91,12 +109,14 @@
    m_btnCurTask.SetFrameColor(m_crBkgnd);
    m_btnCurTask.SetFrameColor(BS_HOVER, RGB(218, 218, 218));
    m_btnCurTask.SetFrameColor(BS_PRESS, RGB(168, 168, 168));
    m_btnCurTask.SetTextColor(m_crForeground);
    m_btnCim.SubclassDlgItem(IDC_BUTTON_CIM, this);
    m_btnCim.SetFaceColor(m_crBkgnd);
    m_btnCim.SetFrameColor(m_crBkgnd);
    m_btnCim.SetFrameColor(BS_HOVER, RGB(218, 218, 218));
    m_btnCim.SetFrameColor(BS_PRESS, RGB(168, 168, 168));
    m_btnCim.SetTextColor(m_crForeground);
    return TRUE;  // return TRUE unless you set the focus to a control
                  // å¼‚常: OCX å±žæ€§é¡µåº”返回 FALSE
@@ -192,4 +212,4 @@
    pItem->GetClientRect(rcItem);
    pItem->MoveWindow(x, (rcClient.Height() - rcItem.Height()) / 2, rcItem.Width(), rcItem.Height());
    x += rcItem.Width();
}
}
SourceCode/Bond/Servo/CMyStatusbar.h
@@ -22,6 +22,8 @@
    void setRunTimeText(const char* pszText);
    void setCurTaskBtnText(const char* pszText);
    void setCimBtnText(const char* pszText);
    void setCurTaskBtnColors(COLORREF face, COLORREF frame, COLORREF text);
    void setCimBtnColors(COLORREF face, COLORREF frame, COLORREF text);
private:
    void Resize();
SourceCode/Bond/Servo/CPanelMaster.cpp
@@ -50,6 +50,11 @@
    return m_nPanelWidth;
}
void CPanelMaster::setPanelWidth(int width)
{
    m_nPanelWidth = width;
}
BOOL CPanelMaster::OnInitDialog()
{
    CDialogEx::OnInitDialog();
@@ -59,13 +64,6 @@
    pLine1->SetBkgndColor(RGB(225, 225, 225));
    pLine1->SetLineColor(RGB(198, 198, 198));
    pLine1->EnableResize();
    // è¯»å–面板宽
    CString strIniFile;
    strIniFile.Format(_T("%s\\%s.ini"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir, (LPTSTR)(LPCTSTR)theApp.m_strAppFile);
    m_nPanelWidth = GetPrivateProfileInt(_T("App"), _T("MasterPanelWidth"),
        int((double)GetSystemMetrics(SM_CXSCREEN) * 0.25), (LPTSTR)(LPCTSTR)strIniFile);
    // treectrl
@@ -132,12 +130,6 @@
    m_nPanelWidth = max(m_nPanelWidth, MASTER_PANEL_MIN_WIDTH);
    m_nPanelWidth = min(m_nPanelWidth, MASTER_PANEL_MAX_WIDTH);
    GetParent()->SendMessage(ID_MSG_PANEL_RESIZE, m_nPanelWidth, 0);
    CString strIniFile, strValue;
    strIniFile.Format(_T("%s\\%s.ini"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir, (LPTSTR)(LPCTSTR)theApp.m_strAppFile);
    strValue.Format(_T("%d"), m_nPanelWidth);
    WritePrivateProfileString(_T("App"), _T("MasterPanelWidth"),
        (LPTSTR)(LPCTSTR)strValue, (LPTSTR)(LPCTSTR)strIniFile);
    OnSize(0, 0, 0);
    
    * result = 0;
SourceCode/Bond/Servo/CPanelMaster.h
@@ -12,6 +12,7 @@
    CPanelMaster(CWnd* pParent = nullptr);   // æ ‡å‡†æž„造函数
    virtual ~CPanelMaster();
    int getPanelWidth();
    void setPanelWidth(int width);
    void loadEquipmentList();
    void loadSteps(SERVO::CEquipment* pEquipment, HTREEITEM hItemEq);
    SERVO::CEquipment* GetActiveEquipment();
SourceCode/Bond/Servo/CPanelProduction.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,198 @@
// CPanelProduction.cpp
//
#include "stdafx.h"
#include "Servo.h"
#include "CPanelProduction.h"
#include "afxdialogex.h"
#include "Common.h"
#include "VerticalLine.h"
// CPanelProduction dialog
IMPLEMENT_DYNAMIC(CPanelProduction, CDialogEx)
CPanelProduction::CPanelProduction(CWnd* pParent /*=nullptr*/)
    : CDialogEx(IDD_PANEL_PRODUCTION, pParent)
{
    m_crBkgnd = PANEL_PRODUCTION_BACKGROUND_COLOR;
    m_hbrBkgnd = nullptr;
    m_nPanelWidth = 288;
    m_hPlaceholder = nullptr;
    m_bShiftSummaryValid = FALSE;
    m_pStatsThread = nullptr;
}
CPanelProduction::~CPanelProduction()
{
}
void CPanelProduction::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CPanelProduction, CDialogEx)
    ON_WM_CTLCOLOR()
    ON_WM_DESTROY()
    ON_WM_SIZE()
    ON_NOTIFY(BYVERTICALLINE_MOVEX, IDC_LINE1, &CPanelProduction::OnVLineMoveX)
    ON_BN_CLICKED(IDC_BUTTON_CLOSE, &CPanelProduction::OnBnClickedButtonClose)
    ON_WM_TIMER()
END_MESSAGE_MAP()
int CPanelProduction::getPanelWidth()
{
    return m_nPanelWidth;
}
void CPanelProduction::setPanelWidth(int width)
{
    m_nPanelWidth = width;
}
BOOL CPanelProduction::OnInitDialog()
{
    CDialogEx::OnInitDialog();
    CVerticalLine* pLine1 = CVerticalLine::Hook(GetDlgItem(IDC_LINE1)->GetSafeHwnd());
    pLine1->SetBkgndColor(RGB(225, 225, 225));
    pLine1->SetLineColor(RGB(198, 198, 198));
    pLine1->EnableResize();
    SetTimer(1, 1000 * 10, nullptr);
    StartStatsThread();
    return TRUE;  // return TRUE unless you set the focus to a control
                  // Exception: OCX property pages should return FALSE
}
HBRUSH CPanelProduction::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    HBRUSH hbr = CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);
    if (nCtlColor == CTLCOLOR_STATIC) {
        pDC->SetBkColor(m_crBkgnd);
        pDC->SetTextColor(RGB(0, 0, 0));
    }
    if (m_hbrBkgnd == nullptr) {
        m_hbrBkgnd = CreateSolidBrush(m_crBkgnd);
    }
    return m_hbrBkgnd;
}
void CPanelProduction::OnDestroy()
{
    StopStatsThread();
    CDialogEx::OnDestroy();
    if (m_hbrBkgnd != nullptr) {
        ::DeleteObject(m_hbrBkgnd);
    }
}
void CPanelProduction::OnSize(UINT nType, int cx, int cy)
{
    CDialogEx::OnSize(nType, cx, cy);
    if (GetDlgItem(IDC_LINE1) == nullptr) return;
    CWnd* pItem;
    CRect rcClient, rcItem;
    GetClientRect(&rcClient);
    pItem = GetDlgItem(IDC_LINE1);
    pItem->MoveWindow(rcClient.right - 3, 0, 3, rcClient.Height());
}
#define PRODUCTION_PANEL_MIN_WIDTH        88
#define PRODUCTION_PANEL_MAX_WIDTH        588
void CPanelProduction::OnVLineMoveX(NMHDR* nmhdr, LRESULT* result)
{
    BYVERTICALLINE_NMHDR* pNmhdrex = (BYVERTICALLINE_NMHDR*)nmhdr;
    int x = pNmhdrex->dwData;
    m_nPanelWidth += x;
    m_nPanelWidth = max(m_nPanelWidth, PRODUCTION_PANEL_MIN_WIDTH);
    m_nPanelWidth = min(m_nPanelWidth, PRODUCTION_PANEL_MAX_WIDTH);
    GetParent()->SendMessage(ID_MSG_PANEL_RESIZE, m_nPanelWidth, 0);
    OnSize(0, 0, 0);
    *result = 0;
}
void CPanelProduction::OnBnClickedButtonClose()
{
    CWnd* pParent = GetParent();
    if (pParent != nullptr) {
        pParent->PostMessage(WM_COMMAND, ID_MENU_WND_TEST_PANEL, 0);
    }
}
BOOL CPanelProduction::TryGetShiftSummary(ProductionShiftSummary& outSummary)
{
    CSingleLock lock(&m_csShiftSummary, TRUE);
    if (!m_bShiftSummaryValid) return FALSE;
    outSummary = m_shiftSummary;
    return TRUE;
}
void CPanelProduction::StartStatsThread()
{
    if (m_pStatsThread != nullptr) return;
    m_evStopStats.ResetEvent();
    m_pStatsThread = AfxBeginThread(&CPanelProduction::StatsThreadProc, this, THREAD_PRIORITY_BELOW_NORMAL, 0, 0);
    if (m_pStatsThread != nullptr) {
        m_pStatsThread->m_bAutoDelete = FALSE;
    }
}
void CPanelProduction::StopStatsThread()
{
    if (m_pStatsThread == nullptr) return;
    m_evStopStats.SetEvent();
    const DWORD rc = WaitForSingleObject(m_pStatsThread->m_hThread, 5000);
    if (rc == WAIT_OBJECT_0) {
        delete m_pStatsThread;
    }
    m_pStatsThread = nullptr;
}
UINT CPanelProduction::StatsThreadProc(LPVOID pParam)
{
    CPanelProduction* self = reinterpret_cast<CPanelProduction*>(pParam);
    if (self == nullptr) return 0;
    const DWORD intervalMs = 5000;
    for (;;) {
        if (self->m_evStopStats.Lock(intervalMs)) break;
        ProductionShiftSummary summary;
        if (ProductionStats::ComputeCurrentShiftSummary(theApp.m_model.m_configuration, summary)) {
            CSingleLock lock(&self->m_csShiftSummary, TRUE);
            self->m_shiftSummary = std::move(summary);
            self->m_bShiftSummaryValid = TRUE;
        }
    }
    return 0;
}
void CPanelProduction::OnTimer(UINT_PTR nIDEvent)
{
    // TODO: åœ¨æ­¤æ·»åŠ æ¶ˆæ¯å¤„ç†ç¨‹åºä»£ç å’Œ/或调用默认值
    if (nIDEvent == 1) {
        ProductionShiftSummary outSummary;
        if (TryGetShiftSummary(outSummary)) {
            TRACE("OnTimer outSummary.output.pairsPass:%d\n", outSummary.output.pairsPass);
        }
    }
    CDialogEx::OnTimer(nIDEvent);
}
SourceCode/Bond/Servo/CPanelProduction.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
#pragma once
#include "BlButton.h"
#include <afxmt.h>
#include "ProductionStats.h"
// CPanelProduction dialog
class CPanelProduction : public CDialogEx
{
    DECLARE_DYNAMIC(CPanelProduction)
public:
    CPanelProduction(CWnd* pParent = nullptr);   // standard constructor
    virtual ~CPanelProduction();
    int getPanelWidth();
    void setPanelWidth(int width);
private:
    COLORREF m_crBkgnd;
    HBRUSH m_hbrBkgnd;
    int m_nPanelWidth;
    CBlButton m_btnClose;
    HWND m_hPlaceholder;
    // Production shift summary (updated by background thread)
    ProductionShiftSummary m_shiftSummary;
    BOOL m_bShiftSummaryValid;
    CCriticalSection m_csShiftSummary;
    CWinThread* m_pStatsThread;
    CEvent m_evStopStats;
protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
// å¯¹è¯æ¡†æ•°æ®
#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_PANEL_PRODUCTION };
#endif
    DECLARE_MESSAGE_MAP()
public:
    virtual BOOL OnInitDialog();
    afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
    afx_msg void OnDestroy();
    afx_msg void OnSize(UINT nType, int cx, int cy);
    afx_msg void OnVLineMoveX(NMHDR* nmhdr, LRESULT* result);
    afx_msg void OnBnClickedButtonClose();
    // Thread-safe snapshot for UI timer display
    BOOL TryGetShiftSummary(ProductionShiftSummary& outSummary);
private:
    static UINT AFX_CDECL StatsThreadProc(LPVOID pParam);
    void StartStatsThread();
    void StopStatsThread();
public:
    afx_msg void OnTimer(UINT_PTR nIDEvent);
};
SourceCode/Bond/Servo/Common.h
@@ -29,6 +29,7 @@
#define APPDLG_BACKGROUND_COLOR                RGB(255, 255, 255)
#define LOGDLG_BACKGROUND_COLOR                RGB(255, 255, 255)
#define PANEL_MASTER_BACKGROUND_COLOR        RGB(255, 255, 255)
#define PANEL_PRODUCTION_BACKGROUND_COLOR    RGB(255, 255, 255)
#define PANEL_ATTRIBUTES_BACKGROUND_COLOR    RGB(255, 255, 255)
#define PANEL_EQUIPMENT_BACKGROUND_COLOR    RGB(255, 255, 255)
#define PAGE_GRPAH1_BACKGROUND_COLOR        RGB(255, 255, 255)
@@ -46,6 +47,8 @@
#define STATUSBAR_BK_STARTING                RGB(58, 127, 78)
#define STATUSBAR_BK_RUNNING                RGB(34, 177, 76)
#define STATUSBAR_BK_ALARM                    RGB(255, 127, 39)
#define CIM_STATUS_BK_SELECTED                STATUSBAR_BK_RUNNING
#define CIM_STATUS_BK_DISCONNECTED            STATUSBAR_BK_NORMAL
/* LOG BTN */
#define BTN_LOG_FRAME_NORMAL            RGB(88, 88, 88)
SourceCode/Bond/Servo/Configuration.h
@@ -34,6 +34,13 @@
    int getPortCassetteSnSeed(int port);
    void setPortCassetteSnSeed(int port, int seed);
    // Production shift settings
    // Reads shift start times from ini.
    // - [Production] DayShiftStart=HH:MM (default 08:00)
    // - [Production] NightShiftStart=HH:MM (default DayShiftStart+12h)
    // Returns TRUE if both values are valid (or derived); otherwise FALSE and falls back to defaults.
    BOOL getProductionShiftStartMinutes(int& dayStartMinutes, int& nightStartMinutes);
public:
    void setP2RemoteEqReconnectInterval(int second);
    int getP2RemoteEqReconnectInterval();
SourceCode/Bond/Servo/ConfigurationProduction.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,75 @@
#include "stdafx.h"
#include "Configuration.h"
#include <mutex>
#include <string>
#include <unordered_map>
static bool TryParseHHMM(const std::string& text, int& outMinutes)
{
    int hour = 0;
    int minute = 0;
    if (sscanf_s(text.c_str(), "%d:%d", &hour, &minute) != 2) return false;
    if (hour < 0 || hour >= 24) return false;
    if (minute < 0 || minute >= 60) return false;
    outMinutes = hour * 60 + minute;
    return true;
}
BOOL CConfiguration::getProductionShiftStartMinutes(int& dayStartMinutes, int& nightStartMinutes)
{
    struct CachedShift {
        BOOL ok = FALSE;
        int day = 0;
        int night = 0;
        bool inited = false;
    };
    static std::mutex s_mtx;
    static std::unordered_map<std::string, CachedShift> s_cache;
    const std::string filePath((LPCSTR)(LPCTSTR)m_strFilepath);
    {
        std::lock_guard<std::mutex> g(s_mtx);
        auto it = s_cache.find(filePath);
        if (it != s_cache.end() && it->second.inited) {
            dayStartMinutes = it->second.day;
            nightStartMinutes = it->second.night;
            return it->second.ok;
        }
    }
    char buf[64] = {};
    GetPrivateProfileStringA("Production", "DayShiftStart", "08:00", buf, (DWORD)sizeof(buf), m_strFilepath);
    std::string dayStr(buf);
    GetPrivateProfileStringA("Production", "NightShiftStart", "", buf, (DWORD)sizeof(buf), m_strFilepath);
    std::string nightStr(buf);
    const int kDefaultDay = 8 * 60;
    const int kDefaultNight = 20 * 60;
    bool okDay = TryParseHHMM(dayStr, dayStartMinutes);
    bool okNight = false;
    if (!nightStr.empty()) okNight = TryParseHHMM(nightStr, nightStartMinutes);
    if (!okDay) dayStartMinutes = kDefaultDay;
    if (!okNight) nightStartMinutes = (dayStartMinutes + 12 * 60) % (24 * 60);
    if (dayStartMinutes == nightStartMinutes) {
        dayStartMinutes = kDefaultDay;
        nightStartMinutes = kDefaultNight;
        {
            std::lock_guard<std::mutex> g(s_mtx);
            s_cache[filePath] = CachedShift{ FALSE, dayStartMinutes, nightStartMinutes, true };
        }
        return FALSE;
    }
    const BOOL ok = (okDay && (nightStr.empty() ? TRUE : okNight)) ? TRUE : FALSE;
    {
        std::lock_guard<std::mutex> g(s_mtx);
        s_cache[filePath] = CachedShift{ ok, dayStartMinutes, nightStartMinutes, true };
    }
    return ok;
}
SourceCode/Bond/Servo/HsmsPassive.cpp
@@ -2523,6 +2523,21 @@
    return requestEventReportSend("CarrierID_Readed");
}
int CHsmsPassive::requestEventReportSend_CheckSlotMap()
{
    return requestEventReportSend("CheckSlotMap");
}
int CHsmsPassive::requestEventReportSend_SlotMapVerificationOK()
{
    return requestEventReportSend("SlotMapVerificationOK");
}
int CHsmsPassive::requestEventReportSend_SlotMapVerificationNG()
{
    return requestEventReportSend("SlotMapVerificationNG");
}
int CHsmsPassive::requestEventReportSend_Port_Unload_Ready()
{
    return requestEventReportSend("Port_Unload_Ready");
@@ -2531,6 +2546,11 @@
int CHsmsPassive::requestEventReportSend_Port_Load_Ready()
{
    return requestEventReportSend("Port_Load_Ready");
}
int CHsmsPassive::requestEventReportSend_Port_Ready_To_Release()
{
    return requestEventReportSend("Port_Ready_To_Release");
}
int CHsmsPassive::requestEventReportSend_Port_Blocked()
@@ -2577,6 +2597,5 @@
{
    return requestEventReportSend("OCR_PanelID_Read_OK");
}
SourceCode/Bond/Servo/HsmsPassive.h
@@ -203,8 +203,12 @@
    int requestEventReportSend(unsigned int CEID);
    int requestEventReportSend(const char* pszEventName);
    int requestEventReportSend_CarrierID_Readed();
    int requestEventReportSend_CheckSlotMap();
    int requestEventReportSend_SlotMapVerificationOK();
    int requestEventReportSend_SlotMapVerificationNG();
    int requestEventReportSend_Port_Unload_Ready();
    int requestEventReportSend_Port_Load_Ready();
    int requestEventReportSend_Port_Ready_To_Release();
    int requestEventReportSend_Port_Blocked();
    int requestEventReportSend_PJ_Queued();
    int requestEventReportSend_PJ_Start();
@@ -295,4 +299,3 @@
    // Spooling Config
    std::map<uint16_t, std::set<uint16_t>> m_spoolingConfig;
};
SourceCode/Bond/Servo/Model.cpp
@@ -9,6 +9,7 @@
#include "TransferManager.h"
#include "RecipeManager.h"
#include "GlassLogDb.h"
#include <map>
CModel::CModel()
@@ -172,12 +173,39 @@
                return CAACK_3;
            }
            const unsigned int portIndex = PTN - 1;
            SERVO::CLoadPort* pLoadPort = (SERVO::CLoadPort*)m_master.getEquipment(EQ_ID_LOADPORT1 + portIndex);
            if (_strcmpi(pszCarrierAction, "ProceedWithCarrier") == 0) {
                m_master.proceedWithCarrier(PTN);
                // æ–‡æ¡£æµç¨‹ï¼šProceedWithCarrier ä¹‹åŽè®¾å¤‡è¿›å…¥ Check SlotMap(WFH),
                // çœŸæ­£çš„“开始”由 ProceedWithSlotMap å†³ç­–触发。
                // ä»…当未开启 CompareMapsBeforeProceeding æ—¶ï¼Œæ‰æ²¿ç”¨æ—§é€»è¾‘直接 Start。
                if (pLoadPort == nullptr || !pLoadPort->isCompareMapsBeforeProceeding()) {
                    m_master.proceedWithCarrier(portIndex);
                }
                return CAACK_0;
            }
            else if (_strcmpi(pszCarrierAction, "ProceedWithSlotMap") == 0) {
                // TODO(Host协商):
                // æ–‡æ¡£ä¸­ ProceedWithSlotMap å¯èƒ½ä¼šæºå¸¦ LotID / PanelIDList / SlotMap ç­‰æ•°æ®ï¼ˆæœ€å¤š13片)用于格式校验与绑定。
                // å½“前 S3F17 è§£æžç»“构仅支持 {DATAID, CarrierAction, CarrierID, PTN},尚未实现上述扩展字段的解析/校验。
                // æœªæ¥è‹¥å®¢æˆ·ç¡®è®¤ SECS-II ç»“构,需要在 CHsmsPassive::replyCarrierAction() æ‰©å±•解析并在此处落库/校验。
                // ä»…在 CompareMapsBeforeProceeding å¯ç”¨ï¼ˆHost æ¨¡å¼ï¼‰ä¸‹å…è®¸æ­¤åŠ¨ä½œ
                if (pLoadPort == nullptr || !pLoadPort->isCompareMapsBeforeProceeding()) {
                    strErrorTxt = "rejected - SlotMap check disabled";
                    return CAACK_5;
                }
                m_hsmsPassive.setVariableValue("SlotMapScan", pLoadPort->getScanCassetteMap());
                m_hsmsPassive.setVariableValue("SlotMapDownload", pLoadPort->getDownloadCassetteMap());
                m_hsmsPassive.requestEventReportSend_SlotMapVerificationOK();
                // Host ç¡®è®¤ SlotMap åŽå†å¼€å§‹åŠ å·¥/流程
                m_master.proceedWithCarrier(portIndex);
                return CAACK_0;
            }
            else if (_strcmpi(pszCarrierAction, "CarrierRelease") == 0) {
                m_master.carrierRelease(PTN);
                m_master.carrierRelease(portIndex);
                return CAACK_0;
            }
@@ -297,7 +325,7 @@
    masterListener.onEqVcrEventReport = [&](void* pMaster, SERVO::CEquipment* pEquipment, SERVO::CVcrEventReport* pReport) {
        LOGE("<CModel>onEqVcrEventReport.");
        if (pReport != nullptr) {
            m_hsmsPassive.setVariableValue("PanelStartID", pReport->getGlassId().c_str());
            m_hsmsPassive.setVariableValue("VCRPanelID", pReport->getGlassId().c_str());
            int nRet = m_hsmsPassive.requestEventReportSend_OCR_PanelID_Read_OK();
            if (nRet != ER_NOERROR) {
                LOGE("<CModel>requestEventReportSend_OCR_PanelID_Read_OK failed, ret=%d", nRet);
@@ -401,10 +429,20 @@
    };
    masterListener.onLoadPortStatusChanged = [&] (void* pMaster, SERVO::CEquipment* pEquipment, short status, __int64 data) {
        LOGE("<CModel>onLoadPortStatusChanged. status = %d", status);
        static std::map<int, short> s_prevPortStatus;
        const int eqId = (pEquipment != nullptr) ? pEquipment->getID() : 0;
        const short prevStatus = s_prevPortStatus[eqId];
        s_prevPortStatus[eqId] = status;
        if (status == PORT_INUSE) {
            SERVO::CLoadPort* pLoadPort = dynamic_cast<SERVO::CLoadPort*>(pEquipment);
            if (pLoadPort != nullptr) {
                m_hsmsPassive.setVariableValue("CarrierID", pLoadPort->getCassetteId().c_str());
                if (prevStatus != PORT_INUSE && pLoadPort->isCompareMapsBeforeProceeding()) {
                    // TODO(Host协商):
                    // æ–‡æ¡£ä¸­æ ‡æ˜Žï¼š1-Empty,3-Exist,因此我们可能需要将uint的map转换为list上传
                    m_hsmsPassive.setVariableValue("SlotMap", pLoadPort->getScanCassetteMap());
                    m_hsmsPassive.requestEventReportSend_CheckSlotMap();
                }
            }
            m_hsmsPassive.requestEventReportSend_CarrierID_Readed();
        }
@@ -426,6 +464,10 @@
            SERVO::CLoadPort* pLoadPort = dynamic_cast<SERVO::CLoadPort*>(pEquipment);
            if (pLoadPort != nullptr) {
                m_hsmsPassive.setVariableValue("UnloadReadyPortId", pLoadPort->getID());
                if (prevStatus == PORT_INUSE) {
                    m_hsmsPassive.setVariableValue("ReadyToReleasePortId", pLoadPort->getID());
                    m_hsmsPassive.requestEventReportSend_Port_Ready_To_Release();
                }
            }
            m_hsmsPassive.requestEventReportSend_Port_Unload_Ready();
        }
SourceCode/Bond/Servo/ProductionStats.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,377 @@
#include "stdafx.h"
#include "ProductionStats.h"
#include <algorithm>
#include <ctime>
#include <iomanip>
#include <sstream>
#include <unordered_map>
#include "Configuration.h"
#include "Log.h"
#include "sqlite3.h"
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
static std::string FormatLocal(const std::chrono::system_clock::time_point& tp)
{
    const std::time_t tt = std::chrono::system_clock::to_time_t(tp);
    std::tm tm{};
    localtime_s(&tm, &tt);
    std::ostringstream oss;
    oss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
    return oss.str();
}
static std::string FormatUtcIso(const std::chrono::system_clock::time_point& tp)
{
    const std::time_t tt = std::chrono::system_clock::to_time_t(tp);
    std::tm tm{};
    gmtime_s(&tm, &tt);
    std::ostringstream oss;
    oss << std::put_time(&tm, "%Y-%m-%dT%H:%M:%SZ");
    return oss.str();
}
static bool TryParseLocalTime(const std::string& text, std::chrono::system_clock::time_point& outTp)
{
    if (text.empty()) return false;
    std::tm tm{};
    std::istringstream iss(text);
    iss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
    if (iss.fail()) return false;
    tm.tm_isdst = -1;
    const std::time_t tt = mktime(&tm);
    if (tt == (time_t)-1) return false;
    outTp = std::chrono::system_clock::from_time_t(tt);
    return true;
}
static std::string GetExeDir()
{
    char path[MAX_PATH] = {};
    GetModuleFileNameA(nullptr, path, MAX_PATH);
    std::string exePath(path);
    const size_t pos = exePath.find_last_of("\\/");
    return (pos == std::string::npos) ? std::string() : exePath.substr(0, pos);
}
static bool FileExistsA(const std::string& path)
{
    const DWORD attr = GetFileAttributesA(path.c_str());
    return (attr != INVALID_FILE_ATTRIBUTES) && ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0);
}
static std::string PickDbPath(const std::string& rel1, const std::string& rel2)
{
    const std::string base = GetExeDir();
    const std::string p1 = base + "\\" + rel1;
    if (FileExistsA(p1)) return p1;
    return base + "\\" + rel2;
}
static void ComputeOutputFromProcessDb(
    const ProductionShiftWindow& win,
    ProductionOutputSummary& out)
{
    const std::string dbPath = PickDbPath("db\\process.db", "DB\\process.db");
    sqlite3* db = nullptr;
    if (sqlite3_open_v2(dbPath.c_str(), &db, SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX, nullptr) != SQLITE_OK) {
        if (db) sqlite3_close(db);
        return;
    }
    const char* sql =
        "SELECT class_id, buddy_id, aoi_result "
        "FROM glass_log "
        "WHERE t_end IS NOT NULL AND t_end >= ? AND t_end < ?;";
    sqlite3_stmt* stmt = nullptr;
    if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {
        sqlite3_close(db);
        return;
    }
    sqlite3_bind_text(stmt, 1, win.startUtcIso.c_str(), -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 2, win.endUtcIso.c_str(), -1, SQLITE_TRANSIENT);
    struct PairAgg {
        bool hasPass = false;
        bool hasFail = false;
        bool hasNo = false;
    };
    std::unordered_map<std::string, PairAgg> pairs;
    for (;;) {
        const int rc = sqlite3_step(stmt);
        if (rc == SQLITE_ROW) {
            const char* classId = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
            const char* buddyId = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
            const int aoi = sqlite3_column_int(stmt, 2);
            const std::string a = classId ? classId : "";
            const std::string b = buddyId ? buddyId : "";
            std::string key;
            if (!b.empty()) {
                if (a <= b) key = a + "|" + b;
                else key = b + "|" + a;
            }
            else {
                key = a;
            }
            auto& agg = pairs[key];
            if (aoi == 1) agg.hasPass = true;
            else if (aoi == 2) agg.hasFail = true;
            else agg.hasNo = true;
        }
        else if (rc == SQLITE_DONE) {
            break;
        }
        else {
            break;
        }
    }
    sqlite3_finalize(stmt);
    sqlite3_close(db);
    out.pairsTotal = static_cast<long long>(pairs.size());
    for (const auto& kv : pairs) {
        const auto& agg = kv.second;
        if (agg.hasFail) out.pairsFail++;
        else if (agg.hasPass) out.pairsPass++;
        else out.pairsNoResult++;
    }
    const long long denom = out.pairsPass + out.pairsFail;
    out.yield = (denom > 0) ? (static_cast<double>(out.pairsPass) / static_cast<double>(denom)) : 0.0;
}
static void ComputeAlarmSummaryFromDb(
    const ProductionShiftWindow& win,
    ProductionAlarmSummary& out)
{
    const std::string dbPath = PickDbPath("DB\\AlarmManager.db", "DB\\AlarmManager.db");
    sqlite3* db = nullptr;
    if (sqlite3_open_v2(dbPath.c_str(), &db, SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX, nullptr) != SQLITE_OK) {
        if (db) sqlite3_close(db);
        return;
    }
    // 1) triggered within shift
    {
        const char* sql =
            "SELECT COUNT(1) FROM alarms "
            "WHERE start_time >= ? AND start_time < ?;";
        sqlite3_stmt* stmt = nullptr;
        if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
            sqlite3_bind_text(stmt, 1, win.startLocal.c_str(), -1, SQLITE_TRANSIENT);
            sqlite3_bind_text(stmt, 2, win.endLocal.c_str(), -1, SQLITE_TRANSIENT);
            if (sqlite3_step(stmt) == SQLITE_ROW) out.alarmsTriggered = sqlite3_column_int(stmt, 0);
            sqlite3_finalize(stmt);
        }
    }
    // 2) overlapping (including active)
    {
        const char* sql =
            "SELECT severity_level, start_time, end_time "
            "FROM alarms "
            "WHERE start_time < ? AND (end_time IS NULL OR end_time >= ?);";
        sqlite3_stmt* stmt = nullptr;
        if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
            sqlite3_bind_text(stmt, 1, win.endLocal.c_str(), -1, SQLITE_TRANSIENT);
            sqlite3_bind_text(stmt, 2, win.startLocal.c_str(), -1, SQLITE_TRANSIENT);
            const auto now = std::chrono::system_clock::now();
            for (;;) {
                const int rc = sqlite3_step(stmt);
                if (rc == SQLITE_ROW) {
                    const int severity = sqlite3_column_int(stmt, 0);
                    const char* sStart = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
                    const char* sEnd = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
                    std::chrono::system_clock::time_point aStart{};
                    if (!TryParseLocalTime(sStart ? sStart : "", aStart)) continue;
                    std::chrono::system_clock::time_point aEnd{};
                    bool hasEnd = TryParseLocalTime(sEnd ? sEnd : "", aEnd);
                    if (!hasEnd) aEnd = std::min(now, win.end);
                    const auto clipStart = std::max(aStart, win.start);
                    const auto clipEnd = std::min(aEnd, win.end);
                    if (clipEnd > clipStart) {
                        const auto secs = std::chrono::duration_cast<std::chrono::seconds>(clipEnd - clipStart).count();
                        out.downtimeMinutes += static_cast<double>(secs) / 60.0;
                    }
                    out.bySeverity[severity] += 1;
                    out.alarmsOverlapping += 1;
                }
                else if (rc == SQLITE_DONE) {
                    break;
                }
                else {
                    break;
                }
            }
            sqlite3_finalize(stmt);
        }
    }
    sqlite3_close(db);
}
static void ComputeTransferSummaryFromDb(
    const ProductionShiftWindow& win,
    ProductionTransferSummary& out)
{
    const std::string dbPath = PickDbPath("DB\\TransferManager.db", "DB\\TransferManager.db");
    sqlite3* db = nullptr;
    if (sqlite3_open_v2(dbPath.c_str(), &db, SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX, nullptr) != SQLITE_OK) {
        if (db) sqlite3_close(db);
        return;
    }
    const char* sql =
        "SELECT status, create_time, end_time "
        "FROM transfers "
        "WHERE end_time >= ? AND end_time < ? AND end_time != '';";
    sqlite3_stmt* stmt = nullptr;
    if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {
        sqlite3_close(db);
        return;
    }
    sqlite3_bind_text(stmt, 1, win.startLocal.c_str(), -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 2, win.endLocal.c_str(), -1, SQLITE_TRANSIENT);
    long long totalSecs = 0;
    long long cntSecs = 0;
    for (;;) {
        const int rc = sqlite3_step(stmt);
        if (rc == SQLITE_ROW) {
            const char* sStatus = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
            const char* sCreate = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
            const char* sEnd = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
            const std::string status = sStatus ? sStatus : "";
            out.byStatus[status] += 1;
            out.transfersFinished += 1;
            std::chrono::system_clock::time_point tpCreate{}, tpEnd{};
            if (TryParseLocalTime(sCreate ? sCreate : "", tpCreate) && TryParseLocalTime(sEnd ? sEnd : "", tpEnd) && tpEnd > tpCreate) {
                totalSecs += std::chrono::duration_cast<std::chrono::seconds>(tpEnd - tpCreate).count();
                cntSecs += 1;
            }
        }
        else if (rc == SQLITE_DONE) {
            break;
        }
        else {
            break;
        }
    }
    sqlite3_finalize(stmt);
    sqlite3_close(db);
    out.avgCreateToEndSeconds = (cntSecs > 0) ? (static_cast<double>(totalSecs) / static_cast<double>(cntSecs)) : 0.0;
}
bool ProductionStats::GetCurrentShiftWindow(CConfiguration& config, ProductionShiftWindow& outWindow)
{
    int dayMin = 8 * 60;
    int nightMin = 20 * 60;
    config.getProductionShiftStartMinutes(dayMin, nightMin);
    const auto now = std::chrono::system_clock::now();
    const std::time_t ttNow = std::chrono::system_clock::to_time_t(now);
    std::tm tmNow{};
    localtime_s(&tmNow, &ttNow);
    std::tm tmMid = tmNow;
    tmMid.tm_hour = 0;
    tmMid.tm_min = 0;
    tmMid.tm_sec = 0;
    tmMid.tm_isdst = -1;
    const std::time_t ttMid = mktime(&tmMid);
    if (ttMid == (time_t)-1) return false;
    const auto midnight = std::chrono::system_clock::from_time_t(ttMid);
    const auto startDayToday = midnight + std::chrono::minutes(dayMin);
    const auto startNightToday = midnight + std::chrono::minutes(nightMin);
    const auto startDay = (now >= startDayToday) ? startDayToday : (startDayToday - std::chrono::hours(24));
    const auto startNight = (now >= startNightToday) ? startNightToday : (startNightToday - std::chrono::hours(24));
    ProductionShiftType type = ProductionShiftType::Day;
    auto start = startDay;
    if (startNight > startDay) {
        type = ProductionShiftType::Night;
        start = startNight;
    }
    const int durationMin =
        (type == ProductionShiftType::Day)
        ? ((nightMin - dayMin + 24 * 60) % (24 * 60))
        : ((dayMin - nightMin + 24 * 60) % (24 * 60));
    if (durationMin <= 0) return false;
    outWindow.type = type;
    outWindow.start = start;
    outWindow.end = start + std::chrono::minutes(durationMin);
    outWindow.startLocal = FormatLocal(outWindow.start);
    outWindow.endLocal = FormatLocal(outWindow.end);
    outWindow.startUtcIso = FormatUtcIso(outWindow.start);
    outWindow.endUtcIso = FormatUtcIso(outWindow.end);
    return true;
}
bool ProductionStats::ComputeCurrentShiftSummary(CConfiguration& config, ProductionShiftSummary& outSummary)
{
    ProductionShiftWindow win;
    if (!GetCurrentShiftWindow(config, win)) return false;
    outSummary = ProductionShiftSummary{};
    outSummary.window = win;
    ComputeOutputFromProcessDb(win, outSummary.output);
    ComputeAlarmSummaryFromDb(win, outSummary.alarms);
    ComputeTransferSummaryFromDb(win, outSummary.transfers);
    return true;
}
void ProductionStats::LogCurrentShiftSummary(CConfiguration& config)
{
    ProductionShiftSummary s;
    if (!ComputeCurrentShiftSummary(config, s)) {
        LOGE("<ProductionStats>Failed to compute shift summary.");
        return;
    }
    const char* shiftName = (s.window.type == ProductionShiftType::Day) ? "Day" : "Night";
    LOGI("<ProductionStats>Shift=%s, [%s ~ %s]", shiftName, s.window.startLocal.c_str(), s.window.endLocal.c_str());
    LOGI("<ProductionStats>Output(pairs): total=%lld, pass=%lld, fail=%lld, no_result=%lld, yield=%.2f%%",
        s.output.pairsTotal, s.output.pairsPass, s.output.pairsFail, s.output.pairsNoResult, s.output.yield * 100.0);
    LOGI("<ProductionStats>Alarms: triggered=%d, overlapping=%d, downtime=%.1f min",
        s.alarms.alarmsTriggered, s.alarms.alarmsOverlapping, s.alarms.downtimeMinutes);
    if (!s.alarms.bySeverity.empty()) {
        std::ostringstream oss;
        oss << "<ProductionStats>AlarmsBySeverity:";
        for (const auto& kv : s.alarms.bySeverity) oss << " L" << kv.first << "=" << kv.second;
        LOGI("%s", oss.str().c_str());
    }
    LOGI("<ProductionStats>Transfers: finished=%d, avg(create->end)=%.1fs", s.transfers.transfersFinished, s.transfers.avgCreateToEndSeconds);
    if (!s.transfers.byStatus.empty()) {
        std::ostringstream oss;
        oss << "<ProductionStats>TransfersByStatus:";
        for (const auto& kv : s.transfers.byStatus) oss << " " << kv.first << "=" << kv.second;
        LOGI("%s", oss.str().c_str());
    }
}
SourceCode/Bond/Servo/ProductionStats.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
#pragma once
#include <chrono>
#include <map>
#include <string>
class CConfiguration;
enum class ProductionShiftType : int {
    Day = 1,
    Night = 2
};
struct ProductionShiftWindow {
    ProductionShiftType type{ ProductionShiftType::Day };
    std::chrono::system_clock::time_point start{};
    std::chrono::system_clock::time_point end{};
    std::string startLocal;   // "YYYY-MM-DD HH:MM:SS"
    std::string endLocal;     // "YYYY-MM-DD HH:MM:SS"
    std::string startUtcIso;  // "YYYY-MM-DDTHH:MM:SSZ"
    std::string endUtcIso;    // "YYYY-MM-DDTHH:MM:SSZ"
};
struct ProductionOutputSummary {
    long long pairsTotal = 0;        // "对数"
    long long pairsPass = 0;
    long long pairsFail = 0;
    long long pairsNoResult = 0;
    double yield = 0.0;              // pairsPass / (pairsPass + pairsFail), 0 if denom==0
};
struct ProductionAlarmSummary {
    int alarmsTriggered = 0;         // start_time within shift window
    int alarmsOverlapping = 0;       // overlaps shift window (including active)
    double downtimeMinutes = 0.0;    // overlap minutes (best-effort)
    std::map<int, int> bySeverity;   // severity_level -> count (overlapping)
};
struct ProductionTransferSummary {
    int transfersFinished = 0;        // end_time within shift window
    std::map<std::string, int> byStatus;
    double avgCreateToEndSeconds = 0.0;
};
struct ProductionShiftSummary {
    ProductionShiftWindow window;
    ProductionOutputSummary output;
    ProductionAlarmSummary alarms;
    ProductionTransferSummary transfers;
};
class ProductionStats {
public:
    static bool GetCurrentShiftWindow(CConfiguration& config, ProductionShiftWindow& outWindow);
    static bool ComputeCurrentShiftSummary(CConfiguration& config, ProductionShiftSummary& outSummary);
    static void LogCurrentShiftSummary(CConfiguration& config);
};
SourceCode/Bond/Servo/Servo.rc
@@ -21,6 +21,7 @@
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
#pragma code_page(936)
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
@@ -565,7 +566,7 @@
    LTEXT           "名称:",IDC_STATIC,12,32,50,8
    EDITTEXT        IDC_EDIT_EVT_NAME,70,30,260,14,ES_AUTOHSCROLL
    LTEXT           "描述:",IDC_STATIC,12,52,50,8
    EDITTEXT        IDC_EDIT_EVT_DESC,70,50,260,34,ES_AUTOHSCROLL | ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL
    EDITTEXT        IDC_EDIT_EVT_DESC,70,50,260,34,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | WS_VSCROLL
    LTEXT           "选择Report:",IDC_STATIC_SELECT_VARS,12,90,70,8
    CONTROL         "",IDC_LIST_EVT_RPTS,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | WS_BORDER | WS_TABSTOP,12,100,316,90
    DEFPUSHBUTTON   "确定",IDOK,200,196,50,14
@@ -805,6 +806,14 @@
    DEFPUSHBUTTON   "确定",IDOK,283,238,50,14
    PUSHBUTTON      "取消",IDCANCEL,338,238,50,14
    CONTROL         "",IDC_LIST1,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,381,226
END
IDD_PANEL_PRODUCTION DIALOGEX 0, 0, 311, 254
STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_SYSMENU
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    LTEXT           "Test",IDC_STATIC,95,67,15,8
    CONTROL         "Custom1",IDC_LINE1,"BYVerticalLine",WS_TABSTOP,286,7,18,240
END
@@ -1236,6 +1245,14 @@
        TOPMARGIN, 7
        BOTTOMMARGIN, 252
    END
    IDD_PANEL_PRODUCTION, DIALOG
    BEGIN
        LEFTMARGIN, 7
        RIGHTMARGIN, 304
        TOPMARGIN, 7
        BOTTOMMARGIN, 247
    END
END
#endif    // APSTUDIO_INVOKED
@@ -1485,6 +1502,11 @@
    0
END
IDD_PANEL_PRODUCTION AFX_DIALOG_LAYOUT
BEGIN
    0
END
/////////////////////////////////////////////////////////////////////////////
//
@@ -1544,6 +1566,9 @@
    BEGIN
        MENUITEM "日志窗口(&L)",                    ID_MENU_WND_LOG
        MENUITEM "警告窗口(A)",                     32781
        MENUITEM SEPARATOR
        MENUITEM "测试面板(T)",                     ID_MENU_WND_TEST_PANEL
        MENUITEM "生产面板(P)",                     ID_MENU_WND_PRO_PANEL
    END
    POPUP "帮助(&H)"
    BEGIN
SourceCode/Bond/Servo/Servo.vcxproj
@@ -245,6 +245,8 @@
    <ClInclude Include="CPageLinkSignal.h" />
    <ClInclude Include="CPageReport.h" />
    <ClInclude Include="CPageVarialbles.h" />
    <ClInclude Include="CPanelProduction.h" />
    <ClInclude Include="ProductionStats.h" />
    <ClInclude Include="CParam.h" />
    <ClInclude Include="CCjPage1.h" />
    <ClInclude Include="CProcessDataListDlg.h" />
@@ -466,6 +468,9 @@
    <ClCompile Include="CPageLinkSignal.cpp" />
    <ClCompile Include="CPageReport.cpp" />
    <ClCompile Include="CPageVarialbles.cpp" />
    <ClCompile Include="ConfigurationProduction.cpp" />
    <ClCompile Include="CPanelProduction.cpp" />
    <ClCompile Include="ProductionStats.cpp" />
    <ClCompile Include="CParam.cpp" />
    <ClCompile Include="CCjPage1.cpp" />
    <ClCompile Include="CProcessDataListDlg.cpp" />
SourceCode/Bond/Servo/Servo.vcxproj.filters
@@ -171,13 +171,7 @@
    <ClCompile Include="RecipeDeviceBindDlg.cpp" />
    <ClCompile Include="CCustomCheckBox.cpp" />
    <ClCompile Include="CCollectionEvent.cpp" />
    <ClCompile Include="CEventEditDlg.cpp">
      <Filter>Source Files</Filter>
    </ClCompile>
    <ClCompile Include="CReport.cpp" />
    <ClCompile Include="CReportEditDlg.cpp">
      <Filter>Source Files</Filter>
    </ClCompile>
    <ClCompile Include="CVariable.cpp" />
    <ClCompile Include="CPageVarialbles.cpp" />
    <ClCompile Include="CPageReport.cpp" />
@@ -240,6 +234,11 @@
    <ClCompile Include="CUserEdit2Dlg.cpp" />
    <ClCompile Include="CUserXLogDlg.cpp" />
    <ClCompile Include="CVariableEditDlg2.cpp" />
    <ClCompile Include="CEventEditDlg.cpp" />
    <ClCompile Include="CReportEditDlg.cpp" />
    <ClCompile Include="ConfigurationProduction.cpp" />
    <ClCompile Include="CPanelProduction.cpp" />
    <ClCompile Include="ProductionStats.cpp" />
  </ItemGroup>
  <ItemGroup>
    <ClInclude Include="AlarmManager.h" />
@@ -261,6 +260,7 @@
    <ClInclude Include="stdafx.h" />
    <ClInclude Include="targetver.h" />
    <ClInclude Include="TerminalDisplayDlg.h" />
    <ClInclude Include="ProductionStats.h" />
    <ClInclude Include="SECSRuntimeManager.h" />
    <ClInclude Include="CCLinkPerformance\CCLinkIEControl.h">
      <Filter>CCLinkPerformance</Filter>
@@ -416,13 +416,7 @@
    <ClInclude Include="RecipeDeviceBindDlg.h" />
    <ClInclude Include="CCustomCheckBox.h" />
    <ClInclude Include="CCollectionEvent.h" />
    <ClInclude Include="CEventEditDlg.h">
      <Filter>Header Files</Filter>
    </ClInclude>
    <ClInclude Include="CReport.h" />
    <ClInclude Include="CReportEditDlg.h">
      <Filter>Header Files</Filter>
    </ClInclude>
    <ClInclude Include="CVariable.h" />
    <ClInclude Include="CPageVarialbles.h" />
    <ClInclude Include="CPageReport.h" />
@@ -523,6 +517,9 @@
    <ClInclude Include="CUserEdit2Dlg.h" />
    <ClInclude Include="CUserXLogDlg.h" />
    <ClInclude Include="CVariableEditDlg2.h" />
    <ClInclude Include="CEventEditDlg.h" />
    <ClInclude Include="CReportEditDlg.h" />
    <ClInclude Include="CPanelProduction.h" />
  </ItemGroup>
  <ItemGroup>
    <ResourceCompile Include="Servo.rc" />
SourceCode/Bond/Servo/ServoDlg.cpp
@@ -93,9 +93,11 @@
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
    m_crBkgnd = APPDLG_BACKGROUND_COLOR;
    m_hbrBkgnd = nullptr;
    m_nLeftPanelType = 2;
    m_pTerminalDisplayDlg = nullptr;
    m_pObserver = nullptr;
    m_pPanelMaster = nullptr;
    m_pPanelProduction = nullptr;
    m_pPanelEquipment = nullptr;
    m_pPanelAttributes = nullptr;
    m_pPageGraph1 = nullptr;
@@ -141,6 +143,10 @@
    ON_UPDATE_COMMAND_UI(ID_MENU_TEST_MESSAGE_CLEAR, &CServoDlg::OnUpdateMenuTestMessageClear)
    ON_COMMAND(ID_MENU_TOOLS_CLIENT_LIST, &CServoDlg::OnMenuToolsClientList)
    ON_UPDATE_COMMAND_UI(ID_MENU_TOOLS_CLIENT_LIST, &CServoDlg::OnUpdateMenuToolsClientList)
    ON_COMMAND(ID_MENU_WND_TEST_PANEL, &CServoDlg::OnMenuWndTestPanel)
    ON_UPDATE_COMMAND_UI(ID_MENU_WND_TEST_PANEL, &CServoDlg::OnUpdateMenuWndTestPanel)
    ON_COMMAND(ID_MENU_WND_PRO_PANEL, &CServoDlg::OnMenuWndProPanel)
    ON_UPDATE_COMMAND_UI(ID_MENU_WND_PRO_PANEL, &CServoDlg::OnUpdateMenuWndProPanel)
    ON_COMMAND(ID_MENU_HELP_ABOUT, &CServoDlg::OnMenuHelpAbout)
    ON_WM_INITMENUPOPUP()
    ON_WM_TIMER()
@@ -306,18 +312,18 @@
                if (STATE::NOT_CONNECTED == state) {
                    m_pMyStatusbar->setCimBtnText("Disconnected");
                    //m_labelPassiveState.setBackground(DISCONNECTED_BACKGROUND);
                    //m_labelPassiveState.setForeground(DISCONNECTED_FOREGROUND, TRUE);
                    m_pMyStatusbar->setCimBtnColors(
                        CIM_STATUS_BK_DISCONNECTED, CIM_STATUS_BK_DISCONNECTED, RGB(0, 0, 0));
                }
                else if (STATE::NOT_SELECTED == state) {
                    m_pMyStatusbar->setCimBtnText("Not Selected");
                    //m_labelPassiveState.setBackground(NOT_SELECTED_BACKGROUND);
                    //m_labelPassiveState.setForeground(NOT_SELECTED_FOREGROUND, TRUE);
                    m_pMyStatusbar->setCimBtnColors(
                        CIM_STATUS_BK_DISCONNECTED, CIM_STATUS_BK_DISCONNECTED, RGB(0, 0, 0));
                }
                else if (STATE::SELECTED == state) {
                    m_pMyStatusbar->setCimBtnText("Selected");
                    //m_labelPassiveState.setBackground(SELECTED_BACKGROUND);
                    //m_labelPassiveState.setForeground(SELECTED_FOREGROUND, TRUE);
                    m_pMyStatusbar->setCimBtnColors(
                        CIM_STATUS_BK_SELECTED, CIM_STATUS_BK_SELECTED, RGB(0, 0, 0));
                }
            }
            pAny->release();
@@ -422,10 +428,19 @@
    m_pTab->SetBkgndColor(RGB(222, 222, 222));
    ShowChildPage(0);
    // è¯»å–面板宽
    CString strIniFile;
    strIniFile.Format(_T("%s\\%s.ini"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir, (LPTSTR)(LPCTSTR)theApp.m_strAppFile);
    int nPanelWidth = GetPrivateProfileInt(_T("App"), _T("MasterPanelWidth"),
        int((double)GetSystemMetrics(SM_CXSCREEN) * 0.25), (LPTSTR)(LPCTSTR)strIniFile);
    m_pPanelMaster = new CPanelMaster();
    m_pPanelMaster->setPanelWidth(nPanelWidth);
    m_pPanelMaster->Create(IDD_PANEL_MASTER, this);
    m_pPanelMaster->ShowWindow(SW_SHOW);
    m_pPanelProduction = new CPanelProduction();
    m_pPanelProduction->setPanelWidth(nPanelWidth);
    m_pPanelProduction->Create(IDD_PANEL_PRODUCTION, this);
    SetLeftPanelType(m_nLeftPanelType, false);
    m_pPanelEquipment = new CPanelEquipment();
    m_pPanelEquipment->Create(IDD_PANEL_EQUIPMENT, this);
    m_pPanelAttributes = new CPanelAttributes();
@@ -685,6 +700,28 @@
    pCmdUI->Enable(TRUE);
}
void CServoDlg::OnMenuWndTestPanel()
{
    SetLeftPanelType(1);
}
void CServoDlg::OnUpdateMenuWndTestPanel(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(TRUE);
    pCmdUI->SetCheck(m_nLeftPanelType == 1);
}
void CServoDlg::OnMenuWndProPanel()
{
    SetLeftPanelType(2);
}
void CServoDlg::OnUpdateMenuWndProPanel(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(TRUE);
    pCmdUI->SetCheck(m_nLeftPanelType == 2);
}
void CServoDlg::OnMenuHelpAbout()
{
    theApp.m_model.getMaster().test();
@@ -754,6 +791,12 @@
        m_pPanelMaster->DestroyWindow();
        delete m_pPanelMaster;
        m_pPanelMaster = nullptr;
    }
    if (m_pPanelProduction != nullptr) {
        m_pPanelProduction->DestroyWindow();
        delete m_pPanelProduction;
        m_pPanelProduction = nullptr;
    }
    if (m_pPanelEquipment != nullptr) {
@@ -856,9 +899,16 @@
    
    int nPanelWidth = 0;
    if (m_pPanelMaster != nullptr) {
    if (m_pPanelMaster != nullptr && ::IsWindow(m_pPanelMaster->GetSafeHwnd())
        && m_pPanelMaster->IsWindowVisible()) {
        nPanelWidth = m_pPanelMaster->getPanelWidth();
        m_pPanelMaster->MoveWindow(x, y, nPanelWidth, y2 - y);
        x += nPanelWidth;
    }
    if (m_pPanelProduction != nullptr && m_pPanelProduction->IsWindowVisible()) {
        nPanelWidth = m_pPanelProduction->getPanelWidth();
        m_pPanelProduction->MoveWindow(x, y, nPanelWidth, y2 - y);
        x += nPanelWidth;
    }
@@ -891,6 +941,32 @@
    m_pMyStatusbar->MoveWindow(0, y2, rcClient.Width(), STATUSBAR_HEIGHT);
}
void CServoDlg::SetLeftPanelType(int type, bool resize)
{
    if (type != 1 && type != 2) {
        type = 1;
    }
    m_nLeftPanelType = type;
    if (m_pPanelMaster != nullptr) {
        m_pPanelMaster->ShowWindow(SW_HIDE);
    }
    if (m_pPanelProduction != nullptr) {
        m_pPanelProduction->ShowWindow(SW_HIDE);
    }
    if (type == 1 && m_pPanelMaster != nullptr) {
        m_pPanelMaster->ShowWindow(SW_SHOW);
    }
    else if (type == 2 && m_pPanelProduction != nullptr) {
        m_pPanelProduction->ShowWindow(SW_SHOW);
    }
    if (resize && ::IsWindow(m_hWnd)) {
        Resize();
        DrawMenuBar();
    }
}
void CServoDlg::OnClose()
@@ -965,7 +1041,20 @@
LRESULT CServoDlg::OnPanelResize(WPARAM wParam, LPARAM lParam)
{
    int width = (int)wParam;
    // m_pPanel->SetPanelWidth(width);
    if (m_pPanelMaster != nullptr) {
        m_pPanelMaster->setPanelWidth(width);
    }
    if (m_pPanelProduction != nullptr) {
        m_pPanelProduction->setPanelWidth(width);
    }
    CString strIniFile, strValue;
    strIniFile.Format(_T("%s\\%s.ini"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir, (LPTSTR)(LPCTSTR)theApp.m_strAppFile);
    strValue.Format(_T("%d"), width);
    WritePrivateProfileString(_T("App"), _T("MasterPanelWidth"),
        (LPTSTR)(LPCTSTR)strValue, (LPTSTR)(LPCTSTR)strIniFile);
    Resize();
    return 0;
@@ -1226,4 +1315,4 @@
    }
    return strText;
}
}
SourceCode/Bond/Servo/ServoDlg.h
@@ -12,6 +12,7 @@
#include "CPanelMaster.h"
#include "CPanelEquipment.h"
#include "CPanelAttributes.h"
#include "CPanelProduction.h"
#include "CPageGraph1.h"
#include "CPageGraph2.h"
#include "TopToolbar.h"
@@ -34,6 +35,7 @@
private:
    void InitRxWindows();
    void Resize();
    void SetLeftPanelType(int type, bool resize = true);
    void ShowChildPage(int index);
    void UpdateLoginStatus();
    CString& GetRuntimeFormatText(CString& strText, const char* pszSuffix);
@@ -64,8 +66,10 @@
    HICON m_hIcon;
    COLORREF m_crBkgnd;
    HBRUSH m_hbrBkgnd;
    int m_nLeftPanelType; // 1: CPanelMaster, 2: CPanelProduction
    CTopToolbar* m_pTopToolbar;
    CPanelMaster* m_pPanelMaster;
    CPanelProduction* m_pPanelProduction;
    CPanelEquipment* m_pPanelEquipment;
    CPanelAttributes* m_pPanelAttributes;
    CMyStatusbar* m_pMyStatusbar;
@@ -100,6 +104,10 @@
    afx_msg void OnUpdateMenuTestMessageClear(CCmdUI* pCmdUI);
    afx_msg void OnMenuToolsClientList();
    afx_msg void OnUpdateMenuToolsClientList(CCmdUI* pCmdUI);
    afx_msg void OnMenuWndTestPanel();
    afx_msg void OnUpdateMenuWndTestPanel(CCmdUI* pCmdUI);
    afx_msg void OnMenuWndProPanel();
    afx_msg void OnUpdateMenuWndProPanel(CCmdUI* pCmdUI);
    afx_msg void OnMenuHelpAbout();
    afx_msg void OnTimer(UINT_PTR nIDEvent);
    afx_msg LRESULT OnPanelResize(WPARAM wParam, LPARAM lParam);
SourceCode/Bond/Servo/resource.h
@@ -44,9 +44,6 @@
#define IDD_DIALOG_CLIENT_LIST          162
#define IDD_DIALOG_LOGIN                163
#define IDD_DIALOG_INPUT                164
#define IDD_DIALOG_VARIABLE_EDIT2       186
#define IDD_DIALOG_REPORT_EDIT          187
#define IDD_DIALOG_EVENT_EDIT           188
#define IDD_PAGE_LINK_SIGNAL            165
#define IDD_DIALOG_SYSTEM_LOG_MANAGER   166
#define IDD_DIALOG_RECIPE_DEVICE_BIND   167
@@ -66,6 +63,10 @@
#define IDD_DIALOG_USER_EDIT2           182
#define IDD_DIALOG1                     184
#define IDD_DIALOG_USERX_LOG            184
#define IDD_DIALOG_VARIABLE_EDIT2       186
#define IDD_DIALOG_REPORT_EDIT          187
#define IDD_DIALOG_EVENT_EDIT           188
#define IDD_PANEL_PRODUCTION            189
#define IDC_SERVO_GRAPH1                1001
#define IDC_BUTTON_LOG                  1002
#define IDC_EDIT_LOG                    1003
@@ -310,21 +311,18 @@
#define IDC_EDIT_USER_PASSWORD          1232
#define IDC_COMBO_USER_ROLE             1233
#define IDC_CHECK_USER_ENABLED          1234
#define IDC_LIST_RPT_VARS               1241
#define IDC_STATIC_SELECT_VARS          1242
#define IDC_EDIT_VAR_ID                 1235
#define IDC_COMBO_VAR_TYPE              1236
#define IDC_EDIT_VAR_NAME               1237
#define IDC_EDIT_VAR_REMARK             1238
#define IDC_EDIT_RPT_ID                 1239
#define IDC_EDIT_RPT_VIDS               1240
#define IDC_LIST_RPT_VARS               1241
#define IDC_STATIC_SELECT_VARS          1242
#define IDC_EDIT_EVT_ID                 1243
#define IDC_EDIT_EVT_NAME               1244
#define IDC_EDIT_EVT_DESC               1245
#define IDC_LIST_EVT_RPTS               1246
#define IDC_EDIT_VAR_ID                1235
#define IDC_COMBO_VAR_TYPE             1236
#define IDC_EDIT_VAR_NAME              1237
#define ID_MENU_HELP_ABOUT              32771
#define ID_MENU_FILE_EXIT               32772
#define ID_MENU_FILE_SECSTEST           32773
@@ -355,12 +353,15 @@
#define ID_EQSGRAPHITEM_TEST6           32798
#define ID_MENU_PROJECT_VARIABLE_LIST   32800
#define ID_MENU_TOOLS_CLIENT_LIST       32801
#define ID_MENU_WND_TEST_PANEL          32802
#define ID_MENU_WND_PRO_PANEL           32803
// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        189
#define _APS_NEXT_COMMAND_VALUE         32802
#define _APS_NEXT_RESOURCE_VALUE        191
#define _APS_NEXT_COMMAND_VALUE         32804
#define _APS_NEXT_CONTROL_VALUE         1247
#define _APS_NEXT_SYMED_VALUE           101
#endif
SourceCode/Bond/x64/Debug/CollectionEventList.txt
@@ -11,7 +11,7 @@
10051,CarrierIDWaitingForHost,,(10051)
10052,CarrierIDVerificationOK,,(10052)
10053,CarrierIDVerificationNG,,(10052)
10061,SlotMapWaitingForHost,,(10061)
10061,CheckSlotMap,,(10061)
10062,SlotMapVerificationOK,,(10062)
10063,SlotMapVerificationNG,,(10062)
10071,GlassIDReadWaitingForHost,,(10071)
@@ -49,4 +49,5 @@
50008,Port_Unload_Ready,,(50008)
50009,Port_Load_Ready,,(50009)
50010,Port_Blocked,,(50010)
50011,TestEvent,TestEvent,测试一下,(50011)
50011,OCR_PanelID_Read_OK,扫码事件上报,(50012)
50012,Port_Ready_To_Release,,(50013)
SourceCode/Bond/x64/Debug/ReportList.txt
@@ -27,3 +27,5 @@
50009,(5011)
50010,(5012)
50011,(5013)
50012,(5014)
50013,(5015)
SourceCode/Bond/x64/Debug/VariableList.txt
@@ -48,3 +48,8 @@
5011,LoadReadyPortId,U1,"Port ID"
5012,BlockedPortId,U1,"Port ID"
5013,TestVID,U1,测试添加变量55
5014,VCRPanelID,A20,Panel id,来自读码器
5015,ReadyToReleasePortId,U1,"Port ID"
10200,SlotMap,U2,SlotMap(Scan)
10201,SlotMapScan,U2,SlotMap(Scan)
10202,SlotMapDownload,U2,SlotMap(Download)