mrDarker
2025-09-11 d64036c0510cf06009a7252e318d828fbc2658f0
Merge branch 'clh' into liuyang
已添加9个文件
已修改47个文件
273614 ■■■■■ 文件已修改
SourceCode/Bond/Servo/CAligner.cpp 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CBakeCooling.cpp 159 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CBakeCooling.h 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CBonder.cpp 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CBonder.h 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEFEM.cpp 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEqAlarmStep.cpp 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEqJobEventStep.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEqPortChangeStep.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEqProcessStep.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEqReadIntStep.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEqReadStep.cpp 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEqStatusStep.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEqVcrEventStep.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEquipment.cpp 114 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEquipment.h 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEquipmentPage3.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CExpandableListCtrl.cpp 265 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CExpandableListCtrl.h 55 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CFliper.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CGlass.cpp 65 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CGlass.h 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CJobDataS.cpp 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CJobDataS.h 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CLoadPort.cpp 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMaster.cpp 79 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMaster.h 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMeasurement.cpp 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMeasurement.h 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageCassetteCtrlCmd.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageGlassList.cpp 1140 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageGlassList.h 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPath.cpp 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPath.h 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CRobotTask.cpp 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CSVData.cpp 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CSVData.h 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CServoUtilsTool.cpp 206 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CServoUtilsTool.h 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CVacuumBake.cpp 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CVacuumBake.h 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/DevicePropertyDlg.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/GlassJson.cpp 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/GlassLogDb.cpp 526 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/GlassLogDb.h 170 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ListCtrlEx.cpp 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Model.cpp 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.rc 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj.filters 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ToolUnits.cpp 220 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ToolUnits.h 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/resource.h 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/sqlite3.c 255811 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/sqlite3.h 13357 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/sqlite3ext.h 719 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CAligner.cpp
@@ -32,7 +32,7 @@
    void CAligner::initPins()
    {
        // åŠ å…¥Pin初始化代码
        LOGI("<CAligner>initPins");
        LOGD("<CAligner>initPins");
        addPin(SERVO::PinType::INPUT, _T("In1"));
        addPin(SERVO::PinType::INPUT, _T("In2"));
        addPin(SERVO::PinType::OUTPUT, _T("Out1"));
@@ -68,6 +68,25 @@
                delete pStep;
            }
        }
        // VCR Event Report
        // æœºå™¨ä¸ŠæŠ¥æ‰«ç ç»“果,扫码器预计安装在巡边检机器上
        {
            CEqReadStep* pStep = new CEqReadStep(0x5fef, 15 * 2,
                [&](void* pFrom, int code, const char* pszData, size_t size) -> int {
                    if (code == ROK && pszData != nullptr && size > 0) {
                        decodeVCREventReport((CStep*)pFrom, pszData, size);
                    }
                    return -1;
                });
            pStep->setName(STEP_EQ_VCR1_EVENT_REPORT);
            pStep->setProp("Port", (void*)1);
            pStep->setWriteSignalDev(0x4a);
            pStep->setReturnDev(0x91e);
            if (addStep(STEP_ID_VCR1_EVENT_REPORT, pStep) != 0) {
                delete pStep;
            }
        }
    }
    void CAligner::onReceiveLBData(const char* pszData, size_t size)
SourceCode/Bond/Servo/CBakeCooling.cpp
@@ -32,7 +32,7 @@
    void CBakeCooling::initPins()
    {
        // åŠ å…¥Pin初始化代码
        LOGI("<CBakeCooling>initPins");
        LOGD("<CBakeCooling>initPins");
        addPin(SERVO::PinType::INPUT, _T("In1"));
        addPin(SERVO::PinType::INPUT, _T("In2"));
        addPin(SERVO::PinType::OUTPUT, _T("Out"));
@@ -334,6 +334,26 @@
            }
        }
        // FAC Data Report
        addFacDataReportStep(0x12589, 0x94d, 1);
        /*
        {
            CEqReadStep* pStep = new CEqReadStep(0x12589, 133 * 2,
                [&](void* pFrom, int code, const char* pszData, size_t size) -> int {
                    if (code == ROK && pszData != nullptr && size > 0) {
                        decodeFacDataReport((CStep*)pFrom, pszData, size);
                    }
                    return -1;
                });
            pStep->setName(STEP_EQ_FAC_DATA_REPORT);
            pStep->setProp("Port", (void*)1);
            pStep->setWriteSignalDev(0x94d);
            if (addStep(STEP_ID_FAC_DATA_REPORT, pStep) != 0) {
                delete pStep;
            }
        }
        */
        // process start/end report
        {
            CEqReadStep* pStep = new CEqReadStep(0x11D3F, 13 * 2,
@@ -465,4 +485,141 @@
        return (int)params.size();
    }
    int CBakeCooling::parsingProcessData(const char* pszData, size_t size, std::vector<CParam>& params)
    {
        return parsingParams(pszData, size, params);
    }
    int CBakeCooling::parsingSVData(const char* pszData, size_t size, std::vector<CParam>& params)
    {
        /*
        1    A烘烤工艺运行步骤    1Word    123456
        2    A烘烤温控表1当前值    2Word    12345.6
        3    A烘烤温控表2当前值    2Word    12345.6
        4    A烘烤温控表4当前值    2Word    12345.6
        5    A烘烤温控表5当前值    2Word    12345.6
        6    A烘烤温控表6当前值    2Word    12345.6
        7    A烘烤温控表7当前值    2Word    12345.6
        8    A烘烤剩余时间    1Word    1234.56
        9    A冷却工艺运行步骤    1Word    123456
        10    A腔冷却剩余时间    1Word    1234.56
        11    B烘烤工艺运行步骤    1Word    123456
        12    B烘烤温控表1当前值    2Word    12345.6
        13    B烘烤温控表2当前值    2Word    12345.6
        14    B烘烤温控表4当前值    2Word    12345.6
        15    B烘烤温控表5当前值    2Word    12345.6
        16    B烘烤温控表6当前值    2Word    12345.6
        17    B烘烤温控表7当前值    2Word    12345.6
        18    B烘烤剩余时间    1Word    1234.56
        19    B冷却工艺运行步骤    1Word    123456
        20    B腔冷却剩余时间    1Word    1234.56
        */
        ASSERT(pszData);
        if (size < 125) return 0;
        int i = 0, v;
        // 1.A烘烤工艺运行步骤
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("A烘烤工艺运行步骤", "", this->getName().c_str(), v));
        i += 2;
        // 2.A烘烤温控表1当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("A烘烤温控表1当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 3.A烘烤温控表2当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("A烘烤温控表2当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 4.A烘烤温控表4当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("A烘烤温控表4当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 5.A烘烤温控表5当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("A烘烤温控表5当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 6.A烘烤温控表6当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("A烘烤温控表6当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 7.A烘烤温控表7当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("A烘烤温控表7当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 8.A烘烤剩余时间
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("A烘烤剩余时间", "", this->getName().c_str(), v * 0.01f));
        i += 2;
        // 9.A冷却工艺运行步骤
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("A冷却工艺运行步骤", "", this->getName().c_str(), v));
        i += 2;
        // 10.A腔冷却剩余时间
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("A腔冷却剩余时间", "", this->getName().c_str(), v * 0.01f));
        i += 2;
        // 11.B烘烤工艺运行步骤
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("B烘烤工艺运行步骤", "", this->getName().c_str(), v));
        i += 2;
        // 12.B烘烤温控表1当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("B烘烤温控表1当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 13.B烘烤温控表2当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("B烘烤温控表2当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 14.B烘烤温控表4当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("B烘烤温控表4当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 15.B烘烤温控表5当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("B烘烤温控表5当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 16.B烘烤温控表6当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("B烘烤温控表6当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 17.B烘烤温控表7当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("B烘烤温控表7当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 18.B烘烤剩余时间
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("B烘烤剩余时间", "", this->getName().c_str(), v * 0.01f));
        i += 2;
        // 19.B冷却工艺运行步骤
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("B冷却工艺运行步骤", "", this->getName().c_str(), v));
        i += 2;
        // 20.B腔冷却剩余时间
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("B腔冷却剩余时间", "", this->getName().c_str(), v * 0.01f));
        i += 2;
        return (int)params.size();
    }
}
SourceCode/Bond/Servo/CBakeCooling.h
@@ -25,6 +25,8 @@
        virtual short getSlotUnit(short slotNo) { return slotNo % 2 == 1 ? 0 : 1; };
        virtual bool isSlotProcessed(int slot);
        virtual int parsingParams(const char* pszData, size_t size, std::vector<CParam>& parsms);
        virtual int parsingProcessData(const char* pszData, size_t size, std::vector<CParam>& parsms);
        virtual int parsingSVData(const char* pszData, size_t size, std::vector<CParam>& parsms);
    };
}
SourceCode/Bond/Servo/CBonder.cpp
@@ -157,7 +157,6 @@
            }
        }
        {
            CEqDateTimeSetCmdStep* pStep = new CEqDateTimeSetCmdStep();
            pStep->setName(STEP_DATETIME_SET_CMD);
@@ -266,6 +265,7 @@
                }
            }
        }
        {
            // Sent Out Job Report Downstream #1~9
            char szBuffer[256];
@@ -350,9 +350,12 @@
            }
        }
        {
            // FAC Data Report
            CEqReadStep* pStep = new CEqReadStep(m_nIndex == 0 ? 0xA60E : 0xE60E, 108 * 2,
        addFacDataReportStep(m_nIndex == 0 ? 0xA589 : 0xE589,
            m_nIndex == 0 ? 0x34d : 0x64d, 1);
        /*
        {
            CEqReadStep* pStep = new CEqReadStep(m_nIndex == 0 ? 0xA589 : 0xE589, 133 * 2,
                [&](void* pFrom, int code, const char* pszData, size_t size) -> int {
                    if (code == ROK && pszData != nullptr && size > 0) {
                        decodeFacDataReport((CStep*)pFrom, pszData, size);
@@ -366,6 +369,7 @@
                delete pStep;
            }
        }
        */
        // process start/end report
        {
@@ -642,4 +646,134 @@
        return (int)params.size();
    }
    int CBonder::parsingProcessData(const char* pszData, size_t size, std::vector<CParam>& params)
    {
        return parsingParams(pszData, size, params);
    }
    int CBonder::parsingSVData(const char* pszData, size_t size, std::vector<CParam>& params)
    {
        /*
        1    å·¥è‰ºè¿è¡Œæ­¥éª¤    1Word    123456
            2    æ°”囊压力当前    2Word    12345.6
            3    ä¸Šè…”压力合计    1Word    1234.56
            4    ç®¡é“真空规值    FLOAT    123.456
            5    è…”体真空规值    FLOAT    123.456
            6    ä¸Šè…”温度1    1Word    12345.6
            7    ä¸Šè…”温度2    1Word    12345.6
            8    ä¸Šè…”温度3    1Word    12345.6
            9    ä¸Šè…”温度4    1Word    12345.6
            10    ä¸Šè…”温度5    1Word    12345.6
            11    ä¸Šè…”温度6    1Word    12345.6
            12    ä¸‹è…”温度1    1Word    12345.6
            13    ä¸‹è…”温度2    1Word    12345.6
            14    ä¸‹è…”温度3    1Word    12345.6
            15    ä¸‹è…”温度4    1Word    12345.6
            16    ä¸‹è…”温度5    1Word    12345.6
            17    ä¸‹è…”温度6    1Word    12345.6
            18    åŽ‹åˆå‰©ä½™æ—¶é—´    1Word    1234.56
*/
        ASSERT(pszData);
        if (size < 125) return 0;
        int i = 0, v;
        // 1.工艺运行步骤
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("工艺运行步骤", "", this->getName().c_str(), v));
        i += 2;
        // 2.气囊压力当前
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("气囊压力当前", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 3.上腔压力合计
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("上腔压力合计", "", this->getName().c_str(), ((short)v) * 0.01f));
        i += 2;
        // 4.管道真空规值
        params.push_back(CParam("管道真空规值", "", this->getName().c_str(), (double)toFloat(&pszData[i])));
        i += 4;
        // 5.腔体真空规值
        params.push_back(CParam("腔体真空规值", "", this->getName().c_str(), (double)toFloat(&pszData[i])));
        i += 4;
        // 6.上腔温度1
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("上腔温度1", "", this->getName().c_str(), v * 0.1f));
        i += 2;
        // 7.上腔温度2
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("上腔温度2", "", this->getName().c_str(), v * 0.1f));
        i += 2;
        // 8.上腔温度3
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("上腔温度3", "", this->getName().c_str(), v * 0.1f));
        i += 2;
        // 9.上腔温度4
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("上腔温度4", "", this->getName().c_str(), v * 0.1f));
        i += 2;
        // 10.上腔温度5
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("上腔温度5", "", this->getName().c_str(), v * 0.1f));
        i += 2;
        // 11.上腔温度6
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("上腔温度6", "", this->getName().c_str(), v * 0.1f));
        i += 2;
        // 12.下腔温度1
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("下腔温度1", "", this->getName().c_str(), v * 0.1f));
        i += 2;
        // 13.下腔温度2
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("下腔温度2", "", this->getName().c_str(), v * 0.1f));
        i += 2;
        // 14.下腔温度3
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("下腔温度3", "", this->getName().c_str(), v * 0.1f));
        i += 2;
        // 15.下腔温度4
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("下腔温度4", "", this->getName().c_str(), v * 0.1f));
        i += 2;
        // 16.下腔温度5
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("下腔温度5", "", this->getName().c_str(), v * 0.1f));
        i += 2;
        // 17.下腔温度6
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("下腔温度6", "", this->getName().c_str(), v * 0.1f));
        i += 2;
        // 18.加热剩余时间
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("加热剩余时间", "", this->getName().c_str(), v * 0.01f));
        i += 2;
        // 19.压合剩余时间
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("压合剩余时间", "", this->getName().c_str(), v * 0.01f));
        i += 2;
        return (int)params.size();
    }
}
SourceCode/Bond/Servo/CBonder.h
@@ -25,6 +25,8 @@
        virtual int onProcessStateChanged(PROCESS_STATE state);
        virtual int getIndexerOperationModeBaseValue();
        virtual int parsingParams(const char* pszData, size_t size, std::vector<CParam>& parsms);
        virtual int parsingProcessData(const char* pszData, size_t size, std::vector<CParam>& parsms);
        virtual int parsingSVData(const char* pszData, size_t size, std::vector<CParam>& parsms);
    public:
        void setIndex(unsigned int index);
SourceCode/Bond/Servo/CEFEM.cpp
@@ -133,7 +133,7 @@
                    LOGI("<CEquipment-%s>发送RobotCmdS成功.", m_strName.c_str());
                }
                else {
                    LOGI("<CEquipment-%s>发送RobotCmds失败,code:%d", m_strName.c_str(), code);
                    LOGE("<CEquipment-%s>发送RobotCmds失败,code:%d", m_strName.c_str(), code);
                }
                return 0;
@@ -367,7 +367,7 @@
    void CEFEM::initPins()
    {
        // åŠ å…¥Pin初始化代码
        LOGI("<CEFEM>initPins");
        LOGD("<CEFEM>initPins");
    }
    void CEFEM::initSteps()
@@ -456,25 +456,6 @@
            pStep->setName(STEP_EQ_CIM_MESSAGE_CONFIRM);
            pStep->setWriteSignalDev(0x49);
            if (addStep(STEP_ID_CIM_MSG_CONFIRM_REPORT, pStep) != 0) {
                delete pStep;
            }
        }
        {
            // VCR Event Report
            // æœºå™¨ä¸ŠæŠ¥æ‰«ç ç»“果,扫码器预计安装在巡边检机器上
            CEqReadStep* pStep = new CEqReadStep(0x5fef, 15 * 2,
                [&](void* pFrom, int code, const char* pszData, size_t size) -> int {
                    if (code == ROK && pszData != nullptr && size > 0) {
                        decodeVCREventReport((CStep*)pFrom, pszData, size);
                    }
                    return -1;
                });
            pStep->setName(STEP_EQ_VCR1_EVENT_REPORT);
            pStep->setProp("Port", (void*)1);
            pStep->setWriteSignalDev(0x4a);
            pStep->setReturnDev(0x91e);
            if (addStep(STEP_ID_VCR1_EVENT_REPORT, pStep) != 0) {
                delete pStep;
            }
        }
@@ -697,9 +678,11 @@
            }
        }
        {
            // FAC Data Report
            CEqReadStep* pStep = new CEqReadStep(0x6301, 108 * 2,
        addFacDataReportStep(0x6589, 0x04d, 1);
        /*
        {
            CEqReadStep* pStep = new CEqReadStep(0x6589, 133 * 2,
                [&](void* pFrom, int code, const char* pszData, size_t size) -> int {
                    if (code == ROK && pszData != nullptr && size > 0) {
                        decodeFacDataReport((CStep*)pFrom, pszData, size);
@@ -713,6 +696,7 @@
                delete pStep;
            }
        }
        */
        {
            // JOB Data Request
@@ -890,7 +874,7 @@
    void CEFEM::printDebugRobotState()
    {
        LOGI("<CEFEM>Robot status:%d, ARM1:%s, ARM2:%s",
        LOGD("<CEFEM>Robot status:%d, ARM1:%s, ARM2:%s",
            m_robotData.status,
            m_robotData.armState[0] ? _T("ON") : _T("OFF"),
            m_robotData.armState[1] ? _T("ON") : _T("OFF"));
SourceCode/Bond/Servo/CEqAlarmStep.cpp
@@ -58,7 +58,7 @@
        m_nAlarmCode = (unsigned int)CToolUnits::toInt16(&szBuffer[6]);
        m_nAlarmLevel = (unsigned int)CToolUnits::toInt16(&szBuffer[8]);
        LOGI("<CEqAlarmStep> Equipment Alarm state Changed<State:%d, Unit:%d, Level:%d, Code:%d, ID:%d>",
        LOGE("<CEqAlarmStep> Equipment Alarm state Changed<State:%d, Unit:%d, Level:%d, Code:%d, ID:%d>",
            m_nAlarmState, m_nUnitId, m_nAlarmLevel, m_nAlarmCode, m_nAlarmId,
            m_strText.c_str(), m_strDescription.c_str());
@@ -68,7 +68,7 @@
    int CEqAlarmStep::onComplete()
    {
        CReadStep::onComplete();
        LOGI("<CEqAlarmStep> onComplete.");
        LOGD("<CEqAlarmStep> onComplete.");
        return 0;
    }
@@ -76,7 +76,7 @@
    int CEqAlarmStep::onTimeout()
    {
        CReadStep::onTimeout();
        LOGI("<CEqAlarmStep> onTimeout.");
        LOGE("<CEqAlarmStep> onTimeout.");
        return 0;
    }
SourceCode/Bond/Servo/CEqJobEventStep.cpp
@@ -57,7 +57,7 @@
    int CEqJobEventStep::onTimeout()
    {
        CReadStep::onTimeout();
        LOGI("<CEqJobEventStep> onTimeout.");
        LOGE("<CEqJobEventStep> onTimeout.");
        return 0;
    }
SourceCode/Bond/Servo/CEqPortChangeStep.cpp
@@ -80,7 +80,7 @@
    int CEqPortChangeStep::onTimeout()
    {
        CReadStep::onTimeout();
        LOGI("<CEQPortChangeStep> onTimeout.");
        LOGE("<CEQPortChangeStep> onTimeout.");
        return 0;
    }
SourceCode/Bond/Servo/CEqProcessStep.cpp
@@ -119,7 +119,7 @@
    int CEqProcessStep::onTimeout()
    {
        CReadStep::onTimeout();
        LOGI("<CEqProcessStep> onTimeout.");
        LOGE("<CEqProcessStep> onTimeout.");
        return 0;
    }
SourceCode/Bond/Servo/CEqReadIntStep.cpp
@@ -71,7 +71,7 @@
    int CEqReadIntStep::onTimeout()
    {
        CReadStep::onTimeout();
        LOGI("<CEqReadIntStep> onTimeout.");
        LOGE("<CEqReadIntStep> onTimeout.");
        return 0;
    }
SourceCode/Bond/Servo/CEqReadStep.cpp
@@ -53,14 +53,14 @@
        int nRet = m_pCclink->ReadData2(m_station, DeviceType::W, m_nDataDev,
            (long)min(READ_BUFFER_MAX, m_nReadSize), szBuffer);
        if (0 != nRet) {
            LOGI("<CEqReadStep>Read data error.");
            LOGE("<CEqReadStep>Read data error.");
            if (m_onReadBlock != nullptr) {
                m_onReadBlock(this, RERROR, nullptr, 0);
            }
            return -1;
        }
        LOGI("<CEqReadStep>read data succeed.");
        LOGD("<CEqReadStep>read data succeed.");
        if (m_onReadBlock != nullptr) {
            m_onReadBlock(this, ROK, szBuffer, m_nReadSize);
        }
@@ -72,7 +72,7 @@
    int CEqReadStep::onComplete()
    {
        CReadStep::onComplete();
        LOGI("<CEqReadStep> onComplete.");
        LOGD("<CEqReadStep> onComplete.");
        if (m_onReadBlock != nullptr) {
            m_onReadBlock(this, RCOMPLETE, nullptr, 0);
        }
@@ -83,7 +83,7 @@
    int CEqReadStep::onTimeout()
    {
        CReadStep::onTimeout();
        LOGI("<CEqReadStep> onTimeout.");
        LOGE("<CEqReadStep> onTimeout.");
        if (m_onReadBlock != nullptr) {
            m_onReadBlock(this, RTIMEOUT, nullptr, 0);
        }
SourceCode/Bond/Servo/CEqStatusStep.cpp
@@ -93,7 +93,7 @@
    int CEqStatusStep::onTimeout()
    {
        CReadStep::onTimeout();
        LOGI("<CEqStatusStep> onTimeout.");
        LOGE("<CEqStatusStep> onTimeout.");
        return 0;
    }
SourceCode/Bond/Servo/CEqVcrEventStep.cpp
@@ -70,7 +70,7 @@
    int CEqVcrEventStep::onTimeout()
    {
        CReadStep::onTimeout();
        LOGI("<CEqVcrEventStep> onTimeout.");
        LOGE("<CEqVcrEventStep> onTimeout.");
        return 0;
    }
SourceCode/Bond/Servo/CEquipment.cpp
@@ -490,6 +490,9 @@
        // process data report
        CHECK_READ_STEP_SIGNAL(STEP_ID_PROCESS_DATA_REPORT, pszData, size);
        // FAC Data report
        CHECK_READ_STEP_SIGNAL(STEP_ID_FAC_DATA_REPORT, pszData, size);
        // é…æ–¹æ”¹å˜
        CHECK_READ_STEP_SIGNAL(STEP_ID_CURRENT_RECIPE_CHANGE_REPORT, pszData, size);
        
@@ -836,7 +839,7 @@
        CEquipment* pFromEq = pFromPin->getEquipment();
        ASSERT(pFromEq);
        LOGI("<CEquipment><%s-%s>收到来自<%s.%s>的Intent<%d,%s,0x%x>",
        LOGD("<CEquipment><%s-%s>收到来自<%s.%s>的Intent<%d,%s,0x%x>",
            this->getName().c_str(),
            pPin->getName().c_str(),
            pFromEq->getName().c_str(),
@@ -913,7 +916,9 @@
        ASSERT(pGlass);
        Lock();
        pGlass->addPath(m_nID, getSlotUnit(putSlot));
        pGlass->addPath(m_nID, getSlotUnit(putSlot), putSlot);
        CGlass* pBuddy = pGlass->getBuddy();
        if (pBuddy != nullptr) pBuddy->addPath(m_nID, getSlotUnit(putSlot), putSlot);
        m_slot[putSlot - 1].setContext(pGlass);
        pGlass->release();                // tempFetchOut需要调用一次release
        Unlock();
@@ -987,6 +992,22 @@
        return nullptr;
    }
    int CEquipment::getAllGlass(std::vector<CGlass*>& glasses)
    {
        Lock();
        for (int i = 0; i < SLOT_MAX; i++) {
            if (!m_slot[i].isEnable()) continue;
            CGlass* pGlass = (CGlass*)m_slot[i].getContext();
            if (pGlass != nullptr) {
                pGlass->addRef();
                glasses.push_back(pGlass);
            }
        }
        Unlock();
        return (int)glasses.size();
    }
    CJobDataS* CEquipment::getJobDataSWithCassette(int cassetteSequenceNo, int jobSequenceNo)
@@ -1135,7 +1156,7 @@
                    LOGI("<CEquipment-%s>设置DispatchingMode成功.", m_strName.c_str());
                }
                else {
                    LOGI("<CEquipment-%s>设置DispatchingMode失败,code:%d", m_strName.c_str(), code);
                    LOGE("<CEquipment-%s>设置DispatchingMode失败,code:%d", m_strName.c_str(), code);
                }
                return 0;
@@ -1165,7 +1186,7 @@
                LOGI("<CEquipment-%s>返回值: %d", m_strName.c_str(), retCode);
            }
            else {
                LOGI("<CEquipment-%s>设置indexerOperationMode失败,code:%d", m_strName.c_str(), code);
                LOGE("<CEquipment-%s>设置indexerOperationMode失败,code:%d", m_strName.c_str(), code);
            }
            if (onWritedRetBlock != nullptr) {
@@ -1196,7 +1217,7 @@
            }
            else {
                m_recipesManager.syncFailed();
                LOGI("<CEquipment-%s>请求单元<%d>主配方列表失败,code:%d", m_strName.c_str(), unitNo, code);
                LOGE("<CEquipment-%s>请求单元<%d>主配方列表失败,code:%d", m_strName.c_str(), unitNo, code);
            }
            return 0;
@@ -1231,7 +1252,7 @@
            }
            else {
                m_recipesManager.syncFailed();
                LOGI("<CEquipment-%s>请求单元<%d>主配方参数列表失败,code:%d", m_strName.c_str(), unitNo, code);
                LOGE("<CEquipment-%s>请求单元<%d>主配方参数列表失败,code:%d", m_strName.c_str(), unitNo, code);
            }
            return 0;
@@ -1355,32 +1376,32 @@
    CSlot* CEquipment::getProcessedSlot(MaterialsType putSlotType, BOOL bJobMode/* = FALSE*/)
    {
        for (int i = 0; i < SLOT_MAX; i++) {
            if (m_nTestFlag == 1) LOGI("getProcessedSlot 001");
            if (m_nTestFlag == 1) LOGD("getProcessedSlot 001");
            if (!m_slot[i].isEnable()) continue;
            if (m_nTestFlag == 1) LOGI("getProcessedSlot 002");
            if (m_nTestFlag == 1) LOGD("getProcessedSlot 002");
            if (m_slot[i].isLock()) continue;
            if (m_nTestFlag == 1) LOGI("getProcessedSlot 003");
            if (m_nTestFlag == 1) LOGD("getProcessedSlot 003");
            CGlass* pGlass = (CGlass*)m_slot[i].getContext();
            if (!isSlotProcessed(i)) continue;
            if (m_nTestFlag == 1) LOGI("getProcessedSlot 004");
            if (m_nTestFlag == 1) LOGD("getProcessedSlot 004");
            if (pGlass == nullptr) continue;
            if (m_nTestFlag == 1) LOGI("getProcessedSlot 005");
            if (m_nTestFlag == 1) LOGD("getProcessedSlot 005");
            if (!pGlass->isScheduledForProcessing()) continue;
            if (m_nTestFlag == 1) LOGI("getProcessedSlot 006");
            if (m_nTestFlag == 1) LOGD("getProcessedSlot 006");
            if (bJobMode && pGlass->getProcessJob() == nullptr) continue;
            if (m_nTestFlag == 1) LOGI("getProcessedSlot 007");
            if (m_nTestFlag == 1) LOGD("getProcessedSlot 007");
            if(pGlass->getInspResult(m_nID, 0) == InspResult::Fail) continue;
            int lsPath = m_slot[i].getLinkSignalPath();
            if(!m_bLinkSignalToUpstream[lsPath][SIGNAL_UPSTREAM_INLINE]
                || m_bLinkSignalToUpstream[lsPath][SIGNAL_UPSTREAM_TROUBLE]
                || !m_bLinkSignalToUpstream[lsPath][SIGNAL_INTERLOCK]
                || !m_bLinkSignalToUpstream[lsPath][SIGNAL_SEND_ABLE] ) continue;
            if (m_nTestFlag == 1) LOGI("getProcessedSlot 008");
            if (m_nTestFlag == 1) LOGD("getProcessedSlot 008");
            MaterialsType glassType = pGlass->getType();
            if (glassType == MaterialsType::G1 && putSlotType == MaterialsType::G2) continue;
            if (m_nTestFlag == 1) LOGI("getProcessedSlot 009");
            if (m_nTestFlag == 1) LOGD("getProcessedSlot 009");
            if (glassType == MaterialsType::G2 && putSlotType == MaterialsType::G1) continue;
            if (m_nTestFlag == 1) LOGI("getProcessedSlot 00a");
            if (m_nTestFlag == 1) LOGD("getProcessedSlot 00a");
            return &m_slot[i];
        }
@@ -1545,6 +1566,11 @@
        this->parsingParams((const char*)rawData.data(), rawData.size(), params);
        pGlass->addParams(params);
        // å…³è”çš„Glass也要更新
        CGlass* pBuddy = pGlass->getBuddy();
        if (pBuddy != nullptr) {
            pBuddy->addParams(params);
        }
        return nRet;
    }
@@ -1721,13 +1747,23 @@
            vcrEventReport.getGlassId().c_str());
        // æ›´æ–°Glass的ID
        CGlass* pGlass = getGlassWithCassette(vcrEventReport.getCassetteSequenceNo(),
            vcrEventReport.getJobSequenceNo());
        if (pGlass != nullptr) {
            pGlass->setID(vcrEventReport.getGlassId().c_str());
        }
        // ç¼“å­˜Attribute,用于调试时显示信息
        unsigned int weight = 201;
        CAttributeVector& attrubutes = pStep->attributeVector();
        vcrEventReport.getAttributeVector(attrubutes, weight);
        // 0426, å…ˆå›ºå®šè¿”回1(OK)
        ((CReadStep*)pStep)->setReturnCode((short)VCR_Reply_Code::OK);
        return 0;
    }
@@ -1773,21 +1809,14 @@
    int CEquipment::decodeFacDataReport(CStep* pStep, const char* pszData, size_t size)
    {
        int index = 0;
        std::string strSvTimeRecord, strSvData;
        CToolUnits::convertString(&pszData[index], 8 * 2, strSvTimeRecord);
        index += 128 * 2;
        CToolUnits::convertString(&pszData[index], 100 * 2, strSvData);
        index += 256 * 2;
        CSVData svData;
        int nRet = svData.unserialize(&pszData[0], (int)size);
        if (nRet < 0) return nRet;
        m_svDatas.push_back(svData);
        // ç¼“å­˜Attribute,用于调试时显示信息
        unsigned int weight = 201;
        pStep->addAttribute(new CAttribute("SV Time Record",
            strSvTimeRecord.c_str(), "", weight++));
        pStep->addAttribute(new CAttribute("SV Data",
            strSvData.c_str(), "", weight++));
        if (m_listener.onSVDataReport != nullptr) {
            m_listener.onSVDataReport(this, &svData);
        }
        return 0;
    }
@@ -1861,6 +1890,9 @@
        if (m_processState != PROCESS_STATE::Processing) {
            Lock();
            m_svDatas.clear();
            Unlock();
            setProcessState(PROCESS_STATE::Processing);
        }
@@ -2153,4 +2185,26 @@
        return 0; 
    };
    void CEquipment::addFacDataReportStep(int dataDev, int writeSignalDev, int port)
    {
        CEqReadStep* pStep = new CEqReadStep(dataDev, 133 * 2,
            [&](void* pFrom, int code, const char* pszData, size_t size) -> int {
                if (code == ROK && pszData != nullptr && size > 0) {
                    decodeFacDataReport((CStep*)pFrom, pszData, size);
                }
                return -1;
            });
        pStep->setName(STEP_EQ_FAC_DATA_REPORT);
        pStep->setProp("Port", (void*)port);
        pStep->setWriteSignalDev(writeSignalDev);
        if (addStep(STEP_ID_FAC_DATA_REPORT, pStep) != 0) {
            delete pStep;
        }
    }
    std::vector<SERVO::CSVData>& CEquipment::getSVDatas()
    {
        return m_svDatas;
    }
}
SourceCode/Bond/Servo/CEquipment.h
@@ -36,6 +36,7 @@
#include "CPortStatusReport.h"
#include "CSlot.h"
#include "CParam.h"
#include "CSVData.h"
namespace SERVO {
@@ -57,6 +58,7 @@
    typedef std::function<void(void* pEiuipment, PROCESS_STATE state)> ONPROCESSSTATE;
    typedef std::function<void(void* pEiuipment, short scanMap, short downMap)> ONMAPMISMATCH;
    typedef std::function<void(void* pEiuipment, short status, __int64 data)> ONPORTSTATUSCHANGED;
    typedef struct _EquipmentListener
    {
        ONALIVE                onAlive;
@@ -69,7 +71,7 @@
        ONPROCESSSTATE        onProcessStateChanged;
        ONMAPMISMATCH        onMapMismatch;
        ONPORTSTATUSCHANGED    onPortStatusChanged;
        ONVCREVENTREPORT    onSVDataReport;
    } EquipmentListener;
@@ -158,7 +160,7 @@
        int setDispatchingMode(DISPATCHING_MODE mode, ONWRITED onWritedBlock = nullptr);
        int indexerOperationModeChange(IDNEXER_OPERATION_MODE mode, ONWRITEDRET onWritedRetBlock);
        void printDebugString001();
        std::vector<SERVO::CSVData>& getSVDatas();
        // è¯·æ±‚主配方列表
        // unitNo: 0:local; Others:unit No
@@ -173,6 +175,8 @@
        // è§£æžé…æ–¹å‚数列表
        virtual int parsingParams(const char* pszData, size_t size, std::vector<CParam>& params) { return 0;  };
        virtual int parsingParams(const char* pszData, size_t size, std::string& strOut);
        virtual int parsingProcessData(const char* pszData, size_t size, std::vector<CParam>& params) { return 0; };
        virtual int parsingSVData(const char* pszData, size_t size, std::vector<CParam>& params) { return 0; };
        // èŽ·å–æŒ‡å®šçš„Slot
        CSlot* getSlot(int index);
@@ -202,6 +206,7 @@
        CGlass* getGlassWithCassette(int cassetteSequenceNo, int jobSequenceNo);
        CGlass* getAnyGlass();
        CGlass* getGlass(const char* pszGlassId);
        int getAllGlass(std::vector<CGlass*>& glasses);
        CJobDataS* getJobDataSWithCassette(int cassetteSequenceNo, int jobSequenceNo);
        // éªŒè¯çŽ»ç’ƒå’Œæ§½æ˜¯å¦åŒ¹é…
@@ -264,6 +269,11 @@
        float toFloat(const char* pszAddr);
    protected:
        // éƒ¨åˆ†ä¼˜åŒ–/简化代码、暂实现部分,到时平铺开
        void addFacDataReportStep(int dataDev, int writeSignalDev, int port);
    protected:
        BOOL m_bEnable;
        EquipmentListener m_listener;
        int m_nID;
@@ -296,6 +306,7 @@
        CRecipesManager m_recipesManager;
        CSlot m_slot[SLOT_MAX];
        PROCESS_STATE m_processState;
        std::vector<SERVO::CSVData> m_svDatas;
    private:
        CEquipment* m_pArm;
SourceCode/Bond/Servo/CEquipmentPage3.cpp
@@ -91,7 +91,7 @@
                AfxMessageBox("设置EAS模式成功!");
            }
            else {
                LOGI("<CEquipment-%s>设置DispatchingMode失败,code:%d", m_pEquipment->getName().c_str(), code);
                LOGE("<CEquipment-%s>设置DispatchingMode失败,code:%d", m_pEquipment->getName().c_str(), code);
                AfxMessageBox("设置EAS模式失败!");
            }
SourceCode/Bond/Servo/CExpandableListCtrl.cpp
@@ -17,7 +17,6 @@
    if (CListCtrl::OnCreate(lpCreateStruct) == -1)
        return -1;
    // æŠ¥è¡¨é£Žæ ¼åˆ—举例
    SetExtendedStyle(GetExtendedStyle()
        | LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER);
@@ -26,13 +25,12 @@
void CExpandableListCtrl::PreSubclassWindow()
{
    // æŠ¥è¡¨é£Žæ ¼åˆ—举例
    SetExtendedStyle(GetExtendedStyle()
        | LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER);
    CListCtrl::PreSubclassWindow();
}
// ===== æ ‘ API =====
CExpandableListCtrl::Node* CExpandableListCtrl::InsertRoot(const std::vector<CString>& cols)
{
    auto n = std::make_unique<Node>((int)max(1, (int)cols.size()));
@@ -67,15 +65,12 @@
void CExpandableListCtrl::RebuildVisible()
{
    // 1) é‡å»ºå¯è§åºåˆ—
    m_visible.clear();
    for (auto& r : m_roots) appendVisible(r.get());
    // 2) é‡ç»˜/重填数据
    SetRedraw(FALSE);
    DeleteAllItems();
    // æ’入可见行
    for (int i = 0; i < (int)m_visible.size(); ++i) {
        Node* n = m_visible[i];
        LVITEM lvi{};
@@ -85,11 +80,16 @@
        lvi.pszText = const_cast<LPTSTR>((LPCTSTR)(n->cols.empty() ? _T("") : n->cols[0]));
        InsertItem(&lvi);
        for (int col = 1; col < GetHeaderCtrl()->GetItemCount(); ++col) {
        const int colCount = GetHeaderCtrl() ? GetHeaderCtrl()->GetItemCount() : 1;
        for (int col = 1; col < colCount; ++col) {
            CString txt = (col < (int)n->cols.size()) ? n->cols[col] : _T("");
            SetItemText(i, col, txt);
        }
    }
    // é‡å»ºåŽï¼ŒæŒ‰è¡Œå·é¢œè‰²æ•°ç»„对齐
    m_rowColors.resize(GetItemCount());
    SetRedraw(TRUE);
    Invalidate();
}
@@ -119,6 +119,69 @@
    return m_visible[i];
}
// ===== é¢œè‰² API =====
void CExpandableListCtrl::SetNodeColor(Node* n, COLORREF text, COLORREF bk)
{
    if (!n) return;
    RowColor rc{};
    rc.text = text; rc.bk = bk;
    rc.hasText = (text != CLR_DEFAULT);
    rc.hasBk = (bk != CLR_DEFAULT);
    m_colorByNode[n] = rc;
    for (int i = 0; i < (int)m_visible.size(); ++i) {
        if (m_visible[i] == n) {
            RedrawItems(i, i);
            UpdateWindow();
            return;
        }
    }
}
void CExpandableListCtrl::ClearNodeColor(Node* n)
{
    if (!n) return;
    auto it = m_colorByNode.find(n);
    if (it != m_colorByNode.end()) {
        m_colorByNode.erase(it);
        for (int i = 0; i < (int)m_visible.size(); ++i) {
            if (m_visible[i] == n) {
                RedrawItems(i, i);
                UpdateWindow();
                return;
            }
        }
    }
}
void CExpandableListCtrl::ClearAllColors()
{
    m_colorByNode.clear();
    m_rowColors.clear();
    Invalidate(FALSE);
}
// å…¼å®¹æ—§æŽ¥å£ï¼šæŒ‰â€œå¯è§è¡Œå·â€ç€è‰²
void CExpandableListCtrl::SetItemColor(DWORD_PTR iItem, COLORREF TextColor, COLORREF TextBkColor)
{
    SetItemColorByVisibleIndex((int)iItem, TextColor, TextBkColor);
}
void CExpandableListCtrl::SetItemColorByVisibleIndex(int row, COLORREF text, COLORREF bk)
{
    if (row < 0) return;
    if (row >= (int)m_rowColors.size())
        m_rowColors.resize(row + 1);
    RowColor rc{};
    rc.text = text; rc.bk = bk;
    rc.hasText = (text != CLR_DEFAULT);
    rc.hasBk = (bk != CLR_DEFAULT);
    m_rowColors[row] = rc;
    RedrawItems(row, row);
    UpdateWindow();
}
CRect CExpandableListCtrl::expanderRectForRow(int row) const
{
    CRect rcLabel;
@@ -127,7 +190,7 @@
    Node* n = const_cast<CExpandableListCtrl*>(this)->GetNodeByVisibleIndex(row);
    if (!n || n->children.empty())
        return CRect(0, 0, 0, 0); // å¶å­ä¸å ä½ï¼Œæ–‡æœ¬å°±ä¸ä¼šè¢«å¤šæŽ¨ä¸€æ ¼
        return CRect(0, 0, 0, 0);
    const int indent = n->level;
    const int left = rcLabel.left + m_expanderPadding + indent * 16;
@@ -140,14 +203,44 @@
    );
}
// é¢œè‰²è®¡ç®—:优先 Node*,其次行号;若需要则让系统高亮覆盖
void CExpandableListCtrl::computeColorsForRow(int row, COLORREF& outText, COLORREF& outBk) const
{
    outText = ListView_GetTextColor(const_cast<CExpandableListCtrl*>(this)->m_hWnd);
    outBk = ListView_GetBkColor(const_cast<CExpandableListCtrl*>(this)->m_hWnd);
    const bool selected = (const_cast<CExpandableListCtrl*>(this)->GetItemState(row, LVIS_SELECTED) & LVIS_SELECTED) != 0;
    const bool focusOnCtrl = (const_cast<CExpandableListCtrl*>(this)->GetSafeHwnd() == ::GetFocus());
    if (m_preserveSelHighlight && selected) {
        outBk = GetSysColor(focusOnCtrl ? COLOR_HIGHLIGHT : COLOR_3DFACE);
        outText = GetSysColor(focusOnCtrl ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT);
        return;
    }
    // Node* é¢œè‰²
    if (Node* n = const_cast<CExpandableListCtrl*>(this)->GetNodeByVisibleIndex(row)) {
        auto it = m_colorByNode.find(n);
        if (it != m_colorByNode.end()) {
            if (it->second.hasText) outText = it->second.text;
            if (it->second.hasBk)   outBk = it->second.bk;
            return;
        }
    }
    // è¡Œå·é¢œè‰²
    if (row >= 0 && row < (int)m_rowColors.size()) {
        const RowColor& rc = m_rowColors[row];
        if (rc.hasText) outText = rc.text;
        if (rc.hasBk)   outBk = rc.bk;
    }
}
void CExpandableListCtrl::OnClick(NMHDR* pNMHDR, LRESULT* pResult)
{
    LPNMITEMACTIVATE pia = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
    if (pia->iItem >= 0) {
        CPoint pt = pia->ptAction;
        // å‘½ä¸­å±•开按钮?
        CRect expRc = expanderRectForRow(pia->iItem);
        if (expRc.PtInRect(pt)) {
            Node* n = GetNodeByVisibleIndex(pia->iItem);
@@ -170,8 +263,15 @@
        return;
    case CDDS_ITEMPREPAINT:
    {
        const int row = (int)pCD->nmcd.dwItemSpec;
        COLORREF txt, bk;
        computeColorsForRow(row, txt, bk);
        pCD->clrText = txt;
        pCD->clrTextBk = bk;
        *pResult = CDRF_NOTIFYSUBITEMDRAW;
        return;
    }
    case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
    {
@@ -179,48 +279,34 @@
        const int col = pCD->iSubItem;
        CDC* pDC = CDC::FromHandle(pCD->nmcd.hdc);
        if (col == 0)
        {
            CRect rc; GetSubItemRect(row, 0, LVIR_LABEL, rc);
        // å¦‚果没有树节点(纯平列表),首列也走默认绘制
            Node* n = GetNodeByVisibleIndex(row);
            if (!n) { *pResult = CDRF_DODEFAULT; return; }
        if (col != 0 || !n) {
            *pResult = CDRF_DODEFAULT;
            return;
        }
            // 1) èƒŒæ™¯/前景颜色:按是否选中
            const bool selected = (GetItemState(row, LVIS_SELECTED) & LVIS_SELECTED) != 0;
            const bool focusOnCtrl = (GetSafeHwnd() == ::GetFocus());
            COLORREF bk = selected ? GetSysColor(focusOnCtrl ? COLOR_HIGHLIGHT : COLOR_3DFACE)
                : ListView_GetBkColor(m_hWnd);
            COLORREF txt = selected ? GetSysColor(focusOnCtrl ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT)
                : ListView_GetTextColor(m_hWnd);
        // é¦–列自绘(树模式)
        CRect rc; GetSubItemRect(row, 0, LVIR_LABEL, rc);
            // ä»…在需要时填充背景(避免“黑一片”)
        COLORREF bk, txt;
        computeColorsForRow(row, txt, bk);
            CBrush bkBrush(bk);
            pDC->FillRect(rc, &bkBrush);
            // 2) å±•å¼€/折叠指示(参考旧项目的右对齐坐标法,做像素对齐,纯GDI)
            if (!n->children.empty())
            {
                CRect box = expanderRectForRow(row);
                // ---- å¯è°ƒå‚数:与旧代码命名一致 ----
                // å³ä¾§ç•™ç™½ï¼ˆä¸Žæ–‡æœ¬é—´éš™/网格线保持距离)
                const int ROFFSET = 2;
                // é—­åˆ/展开的“宽度”设置:奇数更顺眼(9/11 éƒ½è¡Œï¼‰
                const int WIDE = max(9, min(min(box.Width(), box.Height()), 13)); // â–¶ çš„边长
                const int WIDE2 = WIDE / 2;                                        // ä¸€åŠ
                const int EXPANDED_WIDE = WIDE;                                           // â–¼ çš„边长
            const int WIDE = max(9, min(min(box.Width(), box.Height()), 13));
            const int WIDE2 = WIDE / 2;
            const int EXPANDED_WIDE = WIDE;
                // è½»å¾®å†…缩,避免贴边(与你旧代码“按钮要刷一下”同效)
                box.DeflateRect(1, 1);
                // ç»Ÿä¸€åšå¶æ•°å¯¹é½ï¼Œå‡å°‘半像素锯齿
                auto even = [](int v) { return (v & 1) ? (v - 1) : v; };
                // è®¡ç®—“自下向上”的基准偏移,与旧 TreeCtrl ä¸€è‡´
                // è¿™é‡Œç”¨ box ä½œä¸º pRect
                POINT pt[3];
                if (n->expanded) {
                    // â–¼
                    int nBottomOffset = (box.Height() - EXPANDED_WIDE) / 2;
                    pt[0].x = box.right - ROFFSET - EXPANDED_WIDE;
                    pt[0].y = box.bottom - nBottomOffset;
@@ -230,9 +316,7 @@
                    pt[2].y = box.bottom - nBottomOffset - EXPANDED_WIDE;
                }
                else {
                    // â–¶
                    int nBottomOffset = (box.Height() - WIDE) / 2;
                    pt[0].x = box.right - ROFFSET - WIDE2;
                    pt[0].y = box.bottom - nBottomOffset - WIDE;
                    pt[1].x = box.right - ROFFSET - WIDE2;
@@ -241,32 +325,24 @@
                    pt[2].y = box.bottom - nBottomOffset - WIDE2;
                }
                // ä»…填充,不描边(描边会加重台阶感);颜色用 txt ä¸Žä¸»é¢˜ä¸€è‡´
                HGDIOBJ oldPen = pDC->SelectObject(GetStockObject(NULL_PEN));
                HBRUSH   hBrush = CreateSolidBrush(txt);
                HGDIOBJ oldBrush = pDC->SelectObject(hBrush);
                pDC->Polygon(pt, 3);
                pDC->SelectObject(oldPen);
                pDC->SelectObject(oldBrush);
                DeleteObject(hBrush);
            }
            // 3) æ–‡æœ¬ï¼šåŸºäºŽé¦–列区域右移(区分是否有子节点)
            const int indentPx = n->level * 14;
            const int baseLeft = rc.left + m_expanderPadding + indentPx;
            CRect textRc = rc;
            if (!n->children.empty()) {
                // æœ‰å­é¡¹ï¼šé¢„留按钮位 + æ–‡æœ¬é—´éš™
                textRc.left = baseLeft + m_expanderSize + m_textGap;
            }
            else {
                // å¶å­è¡Œï¼šä¸é¢„留按钮位,只给一点点叶子间隙(让层级缩进仍然生效)
                constexpr int kLeafGap = 2; // ä½ å¯è°ƒ 0~4
            constexpr int kLeafGap = 2;
                textRc.left = baseLeft + kLeafGap;
            }
@@ -275,39 +351,94 @@
            CString txt0 = n->cols.empty() ? _T("") : n->cols[0];
            pDC->DrawText(txt0, textRc, DT_SINGLELINE | DT_VCENTER | DT_NOPREFIX | DT_END_ELLIPSIS);
            // â€”— ç”»å®Œä¸‰è§’与文本之后,补一条该行的底部横向网格线 â€”—
            // ä»…当开启了 LVS_EX_GRIDLINES æ‰ç»˜åˆ¶
            if (GetExtendedStyle() & LVS_EX_GRIDLINES)
            {
                // ç”¨æ•´è¡Œ bounds,保证横线贯穿所有列的可见宽度
                CRect rcRow;
                GetSubItemRect(row, 0, LVIR_BOUNDS, rcRow);
                // åº•è¾¹ y åæ ‡ï¼ˆä¸Žç³»ç»Ÿç½‘格线对齐)
            CRect rcRow; GetSubItemRect(row, 0, LVIR_BOUNDS, rcRow);
                const int y = rcRow.bottom - 1;
                // é¢œè‰²ä¸Žç³»ç»Ÿé£Žæ ¼æŽ¥è¿‘;若觉得偏浅,可换 COLOR_3DSHADOW
                CPen pen(PS_SOLID, 1, GetSysColor(COLOR_3DLIGHT));
                CPen* oldPen = pDC->SelectObject(&pen);
                // æ¨ªçº¿ä»Žè¡Œå·¦åˆ°è¡Œå³ï¼ˆå½“前可见区域)
                pDC->MoveTo(rcRow.left, y);
                pDC->LineTo(rcRow.right, y);
                pDC->SelectObject(oldPen);
            }
            *pResult = CDRF_SKIPDEFAULT;
            return;
        }
        // å…¶ä»–列默认绘制
        *pResult = CDRF_DODEFAULT;
        return;
    }
    }
    *pResult = CDRF_DODEFAULT;
}
// å…¼å®¹è¡Œä¸ºï¼šåŒæ­¥ SetItemText åˆ° Node->cols;维护行号颜色数组的插入/删除
LRESULT CExpandableListCtrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
    // åŒæ­¥ SetItemText åˆ° Node(A/W å…¼å®¹ï¼‰
    if (message == LVM_SETITEMTEXT
#ifdef LVM_SETITEMTEXTA
        || message == LVM_SETITEMTEXTA
#endif
#ifdef LVM_SETITEMTEXTW
        || message == LVM_SETITEMTEXTW
#endif
        )
    {
        int row = static_cast<int>(wParam);
        LVITEM* p = reinterpret_cast<LVITEM*>(lParam);
        if (p) {
            Node* n = GetNodeByVisibleIndex(row);
            if (n) {
                int sub = p->iSubItem;
                if (sub >= (int)n->cols.size())
                    n->cols.resize(sub + 1);
                CString newText = p->pszText ? p->pszText : _T("");
                n->cols[sub] = newText;
            }
        }
        // ç»§ç»­äº¤ç»™åŸºç±»å¤„理
    }
    LRESULT nRet = CListCtrl::WindowProc(message, wParam, lParam);
    // ç»´æŠ¤è¡Œå·é¢œè‰²æ•°ç»„(兼容旧 SetItemColor)
    if (message == LVM_INSERTITEM) {
        if (nRet != -1) {
            LVITEM* p = (LVITEM*)lParam;
            int pos = p ? p->iItem : (int)nRet;
            if (pos < 0) pos = (int)nRet;
            if (pos > (int)m_rowColors.size()) pos = (int)m_rowColors.size();
            m_rowColors.insert(m_rowColors.begin() + pos, RowColor{}); // é»˜è®¤è‰²
        }
    }
    else if (message == LVM_DELETEITEM) {
        if (nRet != 0) {
            int pos = (int)wParam;
            if (pos >= 0 && pos < (int)m_rowColors.size())
                m_rowColors.erase(m_rowColors.begin() + pos);
        }
    }
    else if (message == LVM_DELETEALLITEMS) {
        if (nRet != 0) {
            m_rowColors.clear();
        }
    }
    return nRet;
}
// CExpandableListCtrl.cpp é‡Œ
void CExpandableListCtrl::ClearTree()
{
    // æ¸…数据
    m_roots.clear();
    m_visible.clear();
    // æ¸…可见项(务必!否则旧页的行会残留)
    SetRedraw(FALSE);
    DeleteAllItems();
    SetRedraw(TRUE);
    Invalidate();
}
SourceCode/Bond/Servo/CExpandableListCtrl.h
@@ -1,6 +1,7 @@
#pragma once
#include <vector>
#include <memory>
#include <unordered_map>
class CExpandableListCtrl : public CListCtrl
{
@@ -13,39 +14,50 @@
        std::vector<CString> cols; // å„列文本
        bool expanded = false;
        int level = 0; // ç¼©è¿›å±‚级
        Node(int nCols = 1) : cols(nCols) {}
    };
    CExpandableListCtrl();
    virtual ~CExpandableListCtrl();
    // æ•°æ®æž„建
    // ===== æ ‘数据构建(需要折叠功能时使用;纯平列表可忽略) =====
    Node* InsertRoot(const std::vector<CString>& cols);
    Node* InsertChild(Node* parent, const std::vector<CString>& cols);
    // å±•å¼€/折叠
    void Expand(Node* n);
    void Collapse(Node* n);
    void Toggle(Node* n);
    // åˆ·æ–°å¯è§åˆ—表
    void RebuildVisible();
    // ä¾¿æ·ï¼šé€šè¿‡å¯è§è¡Œå·å– Node*
    Node* GetNodeByVisibleIndex(int i) const;
    // ===== è¡Œé…è‰² API =====
    // A) æŒ‰ Node* ç€è‰²ï¼ˆç”¨äºŽæ ‘/可折叠场景,索引变化不受影响)
    void SetNodeColor(Node* n, COLORREF text, COLORREF bk);
    void ClearNodeColor(Node* n);
    void ClearAllColors();
    // B) å…¼å®¹æ—§æŽ¥å£ï¼šæŒ‰â€œå¯è§è¡Œå·â€ç€è‰²ï¼ˆä¸Žä½ å½“前页面代码一致)
    void SetItemColor(DWORD_PTR iItem, COLORREF TextColor, COLORREF TextBkColor); // å…¼å®¹ ListCtrlEx
    void SetItemColorByVisibleIndex(int row, COLORREF text, COLORREF bk);         // åŒä¸Šåˆ«å
    // é€‰ä¸­è¡Œæ˜¯å¦ä½¿ç”¨ç³»ç»Ÿé«˜äº®ï¼ˆTRUE,默认);
    // è‹¥ä¸º FALSE,则选中时仍显示自定义颜色
    void SetPreserveSelectionHighlight(BOOL b) { m_preserveSelHighlight = b; }
    // æ¸…除树
    void ClearTree();
protected:
    virtual void PreSubclassWindow();
    afx_msg int  OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void OnClick(NMHDR* pNMHDR, LRESULT* pResult);
    afx_msg void OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult);
    virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam) override;
    DECLARE_MESSAGE_MAP()
private:
    void appendVisible(Node* n);
    CRect expanderRectForRow(int row) const;        // é¦–列展开按钮区域
    virtual void PreSubclassWindow();
protected:
    // æ¶ˆæ¯
    afx_msg int  OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void OnClick(NMHDR* pNMHDR, LRESULT* pResult);
    afx_msg void OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult);
    DECLARE_MESSAGE_MAP()
    void computeColorsForRow(int row, COLORREF& outText, COLORREF& outBk) const;
private:
    std::vector<std::unique_ptr<Node>> m_roots;     // é¡¶å±‚节点
@@ -53,6 +65,17 @@
    int  m_expanderPadding = 6;                     // é¦–列内侧边距
    int  m_expanderSize = 10;                       // å°ä¸‰è§’/方块大小
    int  m_textGap = 6;
    struct RowColor {
        COLORREF text = CLR_DEFAULT;
        COLORREF bk = CLR_DEFAULT;
        bool hasText = false;
        bool hasBk = false;
};
    // é¢œè‰²ï¼ˆä¸¤è·¯æ¥æºï¼Œä¼˜å…ˆçº§ï¼šNode* > è¡Œå·ï¼‰
    std::unordered_map<Node*, RowColor> m_colorByNode; // æ ‘节点颜色
    std::vector<RowColor>               m_rowColors;   // æŒ‰å½“前可见行号的颜色(兼容旧代码)
    BOOL m_preserveSelHighlight = TRUE; // TRUE=选中时优先系统高亮
};
SourceCode/Bond/Servo/CFliper.cpp
@@ -32,7 +32,7 @@
    void CFliper::initPins()
    {
        // åŠ å…¥Pin初始化代码
        LOGI("<CFliper>initPins");
        LOGD("<CFliper>initPins");
        addPin(SERVO::PinType::INPUT, _T("In"));
        addPin(SERVO::PinType::OUTPUT, _T("Out1"));
        addPin(SERVO::PinType::OUTPUT, _T("Out2"));
SourceCode/Bond/Servo/CGlass.cpp
@@ -31,6 +31,7 @@
        if (m_pBuddy != nullptr) {
            m_pBuddy->release();
            m_pBuddy = nullptr;
        }
    }
@@ -50,7 +51,7 @@
        return strText;
    }
    MaterialsType CGlass::getType()
    MaterialsType CGlass::getType() const
    {
        return m_type;
    }
@@ -65,7 +66,7 @@
        m_strID = pszID;
    }
    std::string& CGlass::getID()
    const std::string& CGlass::getID() const
    {
        return m_strID;
    }
@@ -107,7 +108,26 @@
        return m_pPath;
    }
    CPath* CGlass::getPathWithEq(unsigned int nEqId, unsigned int nUnit)
    std::string CGlass::getPathDescription() const
    {
        std::string strOut, strPath;
        char szBuffer[256];
        CPath* pTemp = m_pPath;
        while (pTemp != nullptr) {
            pTemp->getSimpleDescription(strPath);
            if (strPath.compare("ARM1") != 0 && strPath.compare("ARM2") != 0) {
                if (!strOut.empty()) strOut.append(" -> ");
                strOut.append(strPath);
            }
            pTemp = pTemp->getNext();
        }
        return strOut;
    }
    CPath* CGlass::getPathWithEq(unsigned int nEqId, unsigned int nUnit) const
    {
        CPath* pTemp = m_pPath;
        while (pTemp != nullptr) {
@@ -121,9 +141,9 @@
        return nullptr;
    }
    void CGlass::addPath(unsigned int nEqId, unsigned int nUnit)
    void CGlass::addPath(unsigned int nEqId, unsigned int nUnit, unsigned int slot)
    {
        CPath* pPath = new CPath(nEqId, nUnit);
        CPath* pPath = new CPath(nEqId, nUnit, slot);
        if (m_pPath == nullptr) {
            m_pPath = pPath;
        }
@@ -219,9 +239,14 @@
        return m_pBuddy;
    }
    std::string& CGlass::getBuddyId()
    const std::string& CGlass::getBuddyId() const
    {
        return m_strBuddyId;
    }
    void CGlass::setBuddyId(std::string& strId)
    {
        m_strBuddyId = strId;
    }
    int CGlass::processEnd(unsigned int nEqId, unsigned int nUnit)
@@ -250,12 +275,17 @@
        return 0;
    }
    InspResult CGlass::getInspResult(unsigned int nEqId, unsigned int nUnit)
    InspResult CGlass::getInspResult(unsigned int nEqId, unsigned int nUnit) const
    {
        CPath* pPath = getPathWithEq(nEqId, nUnit);
        if (pPath == nullptr) return InspResult::NotInspected;
        return pPath->getInspResult();
    }
    InspResult CGlass::getAOIInspResult() const
    {
        return getInspResult(EQ_ID_MEASUREMENT, 0);
    }
    std::string CGlass::getStateText()
@@ -362,11 +392,13 @@
    void CGlass::markStart()
    {
        m_state = GlsState::InProcess;
        m_tStart = std::chrono::system_clock::now();
    }
    void CGlass::markEnd()
    {
        m_state = GlsState::Completed;
        m_tEnd = std::chrono::system_clock::now();
    }
@@ -379,4 +411,23 @@
    {
        return m_params;
    }
    std::string CGlass::getParamsDescription() const
    {
        std::string strOut;
        char szBuffer[256];
        for (auto p : m_params) {
            if (!strOut.empty()) strOut.append(",");
            if (p.getValueType() == PVT_INT) {
                sprintf_s(szBuffer, 256, "%s:%d", p.getName().c_str(), p.getIntValue());
            }
            else if (p.getValueType() == PVT_DOUBLE) {
                sprintf_s(szBuffer, 256, "%s:%f", p.getName().c_str(), p.getDoubleValue());
            }
            strOut.append(szBuffer);
        }
        return strOut;
    }
}
SourceCode/Bond/Servo/CGlass.h
@@ -33,21 +33,23 @@
    public:
        virtual std::string& getClassName();
        virtual std::string toString();
        short getCassetteSequenceNo() { return m_jobDataS.getCassetteSequenceNo(); }
        short getJobSequenceNo() { return m_jobDataS.getJobSequenceNo(); }
        MaterialsType getType();
        short getCassetteSequenceNo() const { return m_jobDataS.getCassetteSequenceNo(); }
        short getJobSequenceNo() const { return m_jobDataS.getJobSequenceNo(); }
        MaterialsType getType() const;
        void setType(MaterialsType type);
        void setID(const char* pszID);
        std::string& getID();
        const std::string& getID() const;
        void setOriginPort(int port, int slot);
        void getOrginPort(int& port, int& slot);
        BOOL isScheduledForProcessing();
        void setScheduledForProcessing(BOOL bProcessing);
        CProcessJob* getProcessJob();
        void setProcessJob(CProcessJob* pProcessJob);
        CPath* getPathWithEq(unsigned int nEqId, unsigned int nUnit);
        CPath* getPathWithEq(unsigned int nEqId, unsigned int nUnit) const;
        CPath* getPath();
        void addPath(unsigned int nEqId, unsigned int nUnit);
        void addPath(unsigned int nEqId, unsigned int nUnit, unsigned int slot);
        std::string getPathDescription() const;
        std::string getParamsDescription() const;
        void serialize(CArchive& ar);
        void setJobDataS(CJobDataS* pJobDataS);
        void updateJobDataS(CJobDataS* pJobDataS);
@@ -55,11 +57,13 @@
        BOOL setBuddy(CGlass* pGlass);
        BOOL forceSetBuddy(CGlass* pGlass);
        CGlass* getBuddy();
        std::string& getBuddyId();
        const std::string& getBuddyId() const;
        void setBuddyId(std::string& strId);
        int processEnd(unsigned int nEqId, unsigned int nUnit);
        BOOL isProcessed(unsigned int nEqId, unsigned int nUnit);
        int setInspResult(unsigned int nEqId, unsigned int nUnit, InspResult result);
        InspResult getInspResult(unsigned int nEqId, unsigned int nUnit);
        InspResult getInspResult(unsigned int nEqId, unsigned int nUnit) const;
        InspResult getAOIInspResult() const;
    public:
        // æ–°å¢žçŠ¶æ€
SourceCode/Bond/Servo/CJobDataS.cpp
@@ -143,7 +143,7 @@
        m_pOwner = pOwner;
    }
    int CJobDataS::getCassetteSequenceNo()
    int CJobDataS::getCassetteSequenceNo() const
    {
        return m_nCassetteSequenceNo;
    }
@@ -153,7 +153,7 @@
        m_nCassetteSequenceNo = no;
    }
    int CJobDataS::getJobSequenceNo()
    int CJobDataS::getJobSequenceNo() const
    {
        return m_nJobSequenceNo;
    }
SourceCode/Bond/Servo/CJobDataS.h
@@ -18,9 +18,9 @@
        void copy(CJobDataS* pScr);
        void update(CJobDataS* pScr);
        CJobDataB& getJobDataB(CJobDataB& jobDataB);
        int getCassetteSequenceNo();
        int getCassetteSequenceNo() const;
        void setCassetteSequenceNo(int no);
        int getJobSequenceNo();
        int getJobSequenceNo() const;
        void setJobSequenceNo(int no);
        std::string& getLotId();
        void setLotId(const char* pszId);
SourceCode/Bond/Servo/CLoadPort.cpp
@@ -44,7 +44,7 @@
    void CLoadPort::initPins()
    {
        // åŠ å…¥Pin初始化代码
        LOGI("<CLoadPort>initPins");
        LOGD("<CLoadPort>initPins");
        addPin(SERVO::PinType::INPUT, _T("In"));
        addPin(SERVO::PinType::OUTPUT, _T("Out"));
    }
@@ -983,7 +983,7 @@
                LOGI("<CLoadPort-%d>设置Port type成功.", m_nIndex);
            }
            else {
                LOGI("<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);
@@ -1013,7 +1013,7 @@
                LOGI("<CLoadPort-%d>%s Port成功.", m_nIndex, bEnable ? _T("启用") : _T("禁用"));
            }
            else {
                LOGI("<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);
@@ -1042,7 +1042,7 @@
                LOGI("<CLoadPort-%d>设置Port mode成功.", m_nIndex);
            }
            else {
                LOGI("<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);
@@ -1071,7 +1071,7 @@
                LOGI("<CLoadPort-%d>设置Cassette Type成功.", m_nIndex);
            }
            else {
                LOGI("<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);
@@ -1100,7 +1100,7 @@
                LOGI("<CLoadPort-%d>设置Transfer mode成功.", m_nIndex + 1);
            }
            else {
                LOGI("<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);
@@ -1129,7 +1129,7 @@
                LOGI("<CLoadPort-%d>%s Auto Change成功.", m_nIndex, bEnable ? _T("启用") : _T("禁用"));
            }
            else {
                LOGI("<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);
@@ -1209,7 +1209,7 @@
            CGlass* pGlass = theApp.m_model.m_glassPool.allocaGlass();
            pGlass->setOriginPort(m_nIndex, i);
            pGlass->addPath(m_nID, 0);
            pGlass->addPath(m_nID, 0, i + 1);
            pGlass->processEnd(m_nID, 0);
            pGlass->setID(szBuffer);
            pGlass->setType(type);
@@ -1249,7 +1249,7 @@
            CGlass* pGlass = theApp.m_model.m_glassPool.allocaGlass();
            pGlass->setOriginPort(m_nIndex, i);
            pGlass->setScheduledForProcessing(i % 2 == 1);
            pGlass->addPath(m_nID, 0);
            pGlass->addPath(m_nID, 0, i + 1);
            pGlass->processEnd(m_nID, 0);
            pGlass->setID(szBuffer);
            pGlass->setType(m_cassetteType);
@@ -1289,7 +1289,7 @@
            CGlass* pGlass = theApp.m_model.m_glassPool.allocaGlass();
            pGlass->setOriginPort(m_nIndex, nSlotIndex);
            pGlass->addPath(m_nID, 0);
            pGlass->addPath(m_nID, 0, slot.nSlotID);
            pGlass->processEnd(m_nID, 0);
            pGlass->setID(szBuffer);
            pGlass->setType(static_cast<SERVO::MaterialsType>(config.nMaterialType));
SourceCode/Bond/Servo/CMaster.cpp
@@ -131,7 +131,7 @@
            BoardVersion version{};
            int nRet = m_cclink.GetBoardVersion(version);
            if (nRet == 0) {
                LOGI("版本信息:%s.", version.toString().c_str());
                LOGD("版本信息:%s.", version.toString().c_str());
            }
            else {
                LOGE("获取CC-Link版本信息失败.");
@@ -140,7 +140,7 @@
            BoardStatus status;
            nRet = m_cclink.GetBoardStatus(status);
            if (nRet == 0) {
                LOGI("状态:%s.", status.toString().c_str());
                LOGD("状态:%s.", status.toString().c_str());
            }
            else {
                LOGE("获取CC-Link状态失败.");
@@ -422,7 +422,7 @@
                        TRACE("a0001\n", writeCode, retCode);
                    });
                if (nRet != 0) {
                    LOGI("<Master>EFEM切换Start状态失败");
                    LOGE("<Master>EFEM切换Start状态失败");
                    m_strLastError = "EFEM切换Start状态失败.";
                    goto WAIT;
                }
@@ -435,7 +435,7 @@
                        TRACE("a0002\n");
                    });
                if (nRet != 0) {
                    LOGI("<Master>Bonder1切换Start状态失败");
                    LOGE("<Master>Bonder1切换Start状态失败");
                    m_strLastError = "Bonder1切换Start状态失败.";
                    goto WAIT;
                }
@@ -448,7 +448,7 @@
                        TRACE("a0003\n");
                    });
                if (nRet != 0) {
                    LOGI("<Master>Bonder2切换Start状态失败");
                    LOGE("<Master>Bonder2切换Start状态失败");
                    m_strLastError = "Bonder2切换Start状态失败.";
                    goto WAIT;
                }
@@ -461,7 +461,7 @@
                        TRACE("a0004\n");
                    });
                if (nRet != 0) {
                    LOGI("<Master>BakeCooling切换Start状态失败");
                    LOGE("<Master>BakeCooling切换Start状态失败");
                    m_strLastError = "BakeCooling切换Start状态失败.";
                    goto WAIT;
                }
@@ -474,7 +474,7 @@
                        TRACE("a0005\n");
                    });
                if (nRet != 0) {
                    LOGI("<Master>VacuumBake切换Start状态失败");
                    LOGE("<Master>VacuumBake切换Start状态失败");
                    m_strLastError = "VacuumBake切换Start状态失败.";
                    goto WAIT;
                }
@@ -487,7 +487,7 @@
                        TRACE("a0006\n");
                    });
                if (nRet != 0) {
                    LOGI("<Master>Measurement切换Start状态失败");
                    LOGE("<Master>Measurement切换Start状态失败");
                    m_strLastError = "Measurement切换Start状态失败.";
                    goto WAIT;
                }
@@ -501,7 +501,7 @@
                for (int i = 0; i < 6; i++) {
                    if (!bIomcOk[i]) {
                        bIomcOk[6] = FALSE;
                        LOGI("<Master>%s切换Start状态失败", pEq[i]->getName().c_str());
                        LOGE("<Master>%s切换Start状态失败", pEq[i]->getName().c_str());
                    }
                }
                
@@ -545,7 +545,7 @@
                            TRACE("s000%d: ret=%d\n", i + 1, retCode);
                        });
                    if (nRet != 0) {
                        LOGI("<Master>%s切换Stop状态发送失败", pEq[i]->getName().c_str());
                        LOGE("<Master>%s切换Stop状态发送失败", pEq[i]->getName().c_str());
                        m_strLastError = pEq[i]->getName() + "切换Stop状态发送失败.";
                        bIomcOk[i] = FALSE;
                        promises[i].set_value(); // é¿å… wait é˜»å¡ž
@@ -560,7 +560,7 @@
                for (int i = 0; i < 6; ++i) {
                    if (!bIomcOk[i]) {
                        bIomcOk[6] = FALSE;
                        LOGI("<Master>%s切换Stop状态失败", pEq[i]->getName().c_str());
                        LOGE("<Master>%s切换Stop状态失败", pEq[i]->getName().c_str());
                    }
                }
@@ -734,7 +734,7 @@
                if (!rmd.armState[0]) {
                    // m_nTestFlag = 1;
                    if (m_nTestFlag == 1) LOGI("createTransferTask 004df %d, %d", MaterialsType::G1, secondaryType);
                    if (m_nTestFlag == 1) LOGD("createTransferTask 004df %d, %d", MaterialsType::G1, secondaryType);
                    m_pActiveRobotTask = createTransferTask(pAligner, pVacuumBake, MaterialsType::G1, secondaryType);
                    CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
                    m_nTestFlag = 0;
@@ -798,7 +798,7 @@
                CJState state = m_pControlJob->state();
                if (state == CJState::Completed || state == CJState::Aborted || state == CJState::Failed) {
                    // ConrolJpb已完成
                    LOGI("<Master>ControlJob已经完成或失败中断");
                    LOGE("<Master>ControlJob已经完成或失败中断");
                    unlock();
                    continue;
                }
@@ -835,7 +835,7 @@
                    }
                }
                if (m_inProcesJobs.empty()) {
                    LOGI("<Master>选择当前ProcessJob失败!");
                    LOGE("<Master>选择当前ProcessJob失败!");
                    unlock();
                    continue;
                }
@@ -887,7 +887,7 @@
                // Measurement -> LoadPort
                if (rmd.armState[0] || rmd.armState[1]) {
                    LOGI("Arm1 %s, Arm2 %s.", rmd.armState[0] ? _T("不可用") : _T("可用"),
                    LOGD("Arm1 %s, Arm2 %s.", rmd.armState[0] ? _T("不可用") : _T("可用"),
                        rmd.armState[1] ? _T("不可用") : _T("可用"));
                }
                for (int s = 0; s < 4; s++) {
@@ -1312,22 +1312,22 @@
            BOOL bOk = FALSE;
            lock();
            if (m_pActiveRobotTask != nullptr) {
                LOGI("<CMaster>onPreFethedOutJob 0001.");
                LOGD("<CMaster>onPreFethedOutJob 0001.");
                if (m_pActiveRobotTask->getSrcPosition() == p->getID()) {
                    LOGI("<CMaster>onPreFethedOutJob 0002.");
                    LOGD("<CMaster>onPreFethedOutJob 0002.");
                    CGlass* pGlass = p->getGlassFromSlot(m_pActiveRobotTask->getSrcSlot());
                    if (pGlass != nullptr) {
                        LOGI("<CMaster>onPreFethedOutJob 0003.");
                        LOGD("<CMaster>onPreFethedOutJob 0003.");
                        CJobDataS* pJobDataS = pGlass->getJobDataS();
                        if (pJobDataS != nullptr
                            && pJobDataS->getCassetteSequenceNo() == pJobDataB->getCassetteSequenceNo()
                            && pJobDataS->getJobSequenceNo() == pJobDataB->getJobSequenceNo()) {
                            bOk = TRUE;
                            LOGI("<CMaster>onPreFethedOutJob, å·²æ ¡éªŒæ•°æ®ä¸€è‡´æ€§.");
                            LOGD("<CMaster>onPreFethedOutJob, å·²æ ¡éªŒæ•°æ®ä¸€è‡´æ€§.");
                        }
                        LOGI("<CMaster>onPreFethedOutJob 0004.");
                        LOGD("<CMaster>onPreFethedOutJob 0004.");
                        if (pJobDataS != nullptr) {
                            LOGI("<CMaster>onPreFethedOutJob 0005. %d,%d,%d,%d",
                            LOGD("<CMaster>onPreFethedOutJob 0005. %d,%d,%d,%d",
                                pJobDataS->getCassetteSequenceNo(),
                                pJobDataB->getCassetteSequenceNo(),
                                pJobDataS->getJobSequenceNo(),
@@ -1551,6 +1551,26 @@
            if (m_listener.onLoadPortStatusChanged != nullptr) {
                m_listener.onLoadPortStatusChanged(this, (CEquipment*)pEquipment, status, data);
            }
        };
        listener.onSVDataReport = [&](void* pEquipment, void* pData) {
            CSVData* pSVData = (CSVData*)pData;
            auto rawData = pSVData->getSVRawData();
            std::vector<CParam> params;
            ((CEquipment*)pEquipment)->parsingSVData((const char*)rawData.data(), rawData.size(), params);
            std::string strOut;
            char szBuffer[256];
            for (auto p : params) {
                if (!strOut.empty()) strOut.append(",");
                if (p.getValueType() == PVT_INT) {
                    sprintf_s(szBuffer, 256, "%s:%d", p.getName().c_str(), p.getIntValue());
                }
                else if (p.getValueType() == PVT_DOUBLE) {
                    sprintf_s(szBuffer, 256, "%s:%f", p.getName().c_str(), p.getDoubleValue());
                }
                strOut.append(szBuffer);
            }
            LOGD("<CMaster-%s>SVDataReport:%s", ((CEquipment*)pEquipment)->getName().c_str(), strOut.c_str());
        };
        pEquipment->setListener(listener);
        pEquipment->setCcLink(&m_cclink);
@@ -2018,12 +2038,12 @@
        pSrcEq->m_nTestFlag = m_nTestFlag;
        pTarSlot = pTarEq->getAvailableSlotForGlass(primaryType);
        pSrcSlot = pSrcEq->getProcessedSlot(primaryType, bJobMode);
        if (m_nTestFlag == 1) LOGI("createTransferTask 003 %x, %x", pTarSlot, pSrcSlot);
        if (m_nTestFlag == 1) LOGD("createTransferTask 003 %x, %x", pTarSlot, pSrcSlot);
        if (pSrcSlot == nullptr || nullptr == pTarSlot && secondaryType != SERVO::MaterialsType::G0) {
            pTarSlot = pTarEq->getAvailableSlotForGlass(secondaryType);
            pSrcSlot = pSrcEq->getProcessedSlot(secondaryType, bJobMode);
        }
        if (m_nTestFlag == 1) LOGI("createTransferTask 004 %x, %x", pTarSlot, pSrcSlot);
        if (m_nTestFlag == 1) LOGD("createTransferTask 004 %x, %x", pTarSlot, pSrcSlot);
        if (pSrcSlot != nullptr && nullptr != pTarSlot) {
            pTask = new CRobotTask();
@@ -2678,4 +2698,17 @@
        return nullptr;
    }
    int CMaster::getWipGlasses(std::vector<CGlass*>& glasses)
    {
        for (auto eq : m_listEquipment) {
            auto p = dynamic_cast<CLoadPort*>(eq);
            if (p == nullptr) {
                eq->getAllGlass(glasses);
            }
        }
        return (int)glasses.size();
    }
}
SourceCode/Bond/Servo/CMaster.h
@@ -124,6 +124,7 @@
        CLoadPort* getPortWithCarrierId(const std::string& carrierId) const;
        bool saveState() const;
        bool loadState(const std::string& path);
        int getWipGlasses(std::vector<CGlass*>& glasses);
    private:
        inline void lock() { EnterCriticalSection(&m_criticalSection); }
SourceCode/Bond/Servo/CMeasurement.cpp
@@ -334,6 +334,26 @@
            }
        }
        // FAC Data Report
        addFacDataReportStep(0x1a589, 0xf4d, 1);
        /*
        {
            CEqReadStep* pStep = new CEqReadStep(0x1a589, 133 * 2,
                [&](void* pFrom, int code, const char* pszData, size_t size) -> int {
                    if (code == ROK && pszData != nullptr && size > 0) {
                        decodeFacDataReport((CStep*)pFrom, pszData, size);
                    }
                    return -1;
                });
            pStep->setName(STEP_EQ_FAC_DATA_REPORT);
            pStep->setProp("Port", (void*)1);
            pStep->setWriteSignalDev(0xf4d);
            if (addStep(STEP_ID_FAC_DATA_REPORT, pStep) != 0) {
                delete pStep;
            }
        }
        */
        // process start/end report
        {
            CEqReadStep* pStep = new CEqReadStep(0x19D3F, 13 * 2,
@@ -437,4 +457,35 @@
        return (int)params.size();
    }
    int CMeasurement::parsingProcessData(const char* pszData, size_t size, std::vector<CParam>& params)
    {
        return parsingParams(pszData, size, params);
    }
    int CMeasurement::parsingSVData(const char* pszData, size_t size, std::vector<CParam>& params)
    {
        /*/
        1    å·¥è‰ºè¿è¡Œæ­¥éª¤    1Word    123456
            2    AOI检测速度    2Word    123.456
        */
        ASSERT(pszData);
        if (size < 125) return 0;
        int i = 0, v;
        // 1.工艺运行步骤
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("工艺运行步骤", "", this->getName().c_str(), v));
        i += 2;
        // 2.检测速度
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("A腔温控表1当前值", "", this->getName().c_str(), v * 0.001f));
        i += 4;
        return (int)params.size();
    }
}
SourceCode/Bond/Servo/CMeasurement.h
@@ -23,6 +23,8 @@
        virtual int recvIntent(CPin* pPin, CIntent* pIntent);
        virtual int getIndexerOperationModeBaseValue();
        virtual int parsingParams(const char* pszData, size_t size, std::vector<CParam>& parsms);
        virtual int parsingProcessData(const char* pszData, size_t size, std::vector<CParam>& parsms);
        virtual int parsingSVData(const char* pszData, size_t size, std::vector<CParam>& parsms);
    };
}
SourceCode/Bond/Servo/CPageCassetteCtrlCmd.cpp
@@ -119,7 +119,7 @@
                LOGI("sendCassetteCtrlCmd æˆåŠŸ.");
            }
            else {
                LOGI("sendCassetteCtrlCmd å¤±è´¥.");
                LOGE("sendCassetteCtrlCmd å¤±è´¥.");
            }
            return 0;
SourceCode/Bond/Servo/CPageGlassList.cpp
@@ -6,10 +6,339 @@
#include "CPageGlassList.h"
#include "afxdialogex.h"
#include "GlassJson.h"
#include "CServoUtilsTool.h"
#include "ToolUnits.h"
#include <optional>
#include <unordered_set>
#include <unordered_map>
#include <vector>
#include <string>
#define PAGE_SIZE                        100
#define PAGE_SIZE                       50
#define PAGE_BACKGROUND_COLOR            RGB(252, 252, 255)
// WIP é¢œè‰²ï¼šçˆ¶ï¼ˆæ ¹/无 buddy)= åŸºç¡€ç»¿ï¼›å­ï¼ˆbuddy)= æ›´æµ…
static const COLORREF kWipText = RGB(0, 0, 0);
static const COLORREF kWipParentBk = RGB(201, 228, 180); // åŸºç¡€ç»¿
static const COLORREF kWipChildBk = RGB(221, 241, 208); // æ›´æµ…一点
// ===== æ”¾åœ¨ CPageGlassList.cpp é¡¶éƒ¨çš„匿名工具(文件内静态) =====
// æŠŠå½“前“已展开”的父行,用它们的 classId(第4列文本)做 key è®°å½•下来
static std::unordered_set<std::string> SnapshotExpandedKeys(CExpandableListCtrl& lv) {
    std::unordered_set<std::string> keys;
    for (int i = 0; i < lv.GetItemCount(); ++i) {
        auto* n = lv.GetNodeByVisibleIndex(i);
        if (!n || n->children.empty() || !n->expanded) continue;
        if ((int)n->cols.size() > 4) {
#ifdef _UNICODE
            keys.insert(CT2A(n->cols[4]));
#else
            keys.insert(n->cols[4].GetString());
#endif
        }
    }
    return keys;
}
// æ ¹æ®å¿«ç…§æ¢å¤å±•开状态(在你新建完每个“父节点”时调用一次)
static void MaybeRestoreExpandByKey(CExpandableListCtrl::Node* n,
    const std::unordered_set<std::string>& keys) {
    if (!n || (int)n->cols.size() <= 4) return;
#ifdef _UNICODE
    std::string k = CT2A(n->cols[4]);
#else
    std::string k = n->cols[4].GetString();
#endif
    if (keys.find(k) != keys.end())
        n->expanded = true;
}
static void CaptureUiState(CExpandableListCtrl& lv,
    std::vector<CExpandableListCtrl::Node*>& outSel,
    CExpandableListCtrl::Node*& outTopNode)
{
    outSel.clear(); outTopNode = nullptr;
    const int top = lv.GetTopIndex();
    if (top >= 0 && top < lv.GetItemCount())
        outTopNode = lv.GetNodeByVisibleIndex(top);
    for (int i = 0; i < lv.GetItemCount(); ++i) {
        if ((lv.GetItemState(i, LVIS_SELECTED) & LVIS_SELECTED) != 0) {
            auto* n = lv.GetNodeByVisibleIndex(i);
            if (n) outSel.push_back(n);
        }
    }
}
static void RestoreUiState(CExpandableListCtrl& lv,
    const std::vector<CExpandableListCtrl::Node*>& sel,
    CExpandableListCtrl::Node* topNode)
{
    // æ¸…掉现有选择
    for (int i = 0; i < lv.GetItemCount(); ++i)
        lv.SetItemState(i, 0, LVIS_SELECTED);
    // æ¢å¤é€‰æ‹©
    for (auto* n : sel) {
        for (int i = 0; i < lv.GetItemCount(); ++i) {
            if (lv.GetNodeByVisibleIndex(i) == n) {
                lv.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
                break;
            }
        }
    }
    // å°½é‡æŠŠä¹‹å‰çš„顶行滚回可见
    if (topNode) {
        for (int i = 0; i < lv.GetItemCount(); ++i) {
            if (lv.GetNodeByVisibleIndex(i) == topNode) {
                lv.EnsureVisible(i, FALSE);
                break;
            }
        }
    }
}
// ====== å¼€å…³ï¼š1=启用假数据(只替换 DB æŸ¥è¯¢ï¼‰ï¼›0=用真实 DB ======
#define USE_FAKE_DB_DEMO 1
#if USE_FAKE_DB_DEMO
#include <ctime>
#include <atlconv.h>   // CStringA
#include <initializer_list>
#include <string>
#include <vector>
// ---- æ¨¡æ‹Ÿè®°å½•/分页结构(字段与你现有代码一致)----
struct FakeDbRecord {
    int         id;
    int         cassetteSeqNo;
    int         jobSeqNo;
    std::string classId;
    int         materialType;
    int         state;
    std::string tStart;
    std::string tEnd;
    std::string buddyId;
    int         aoiResult;
    std::string path;
    std::string params;
};
struct FakeDbPage { std::vector<FakeDbRecord> items; };
// ---- CString -> std::string(ANSI,本地代码页;仅用于测试模拟)----
static std::string toAnsi(const CString& s) {
#ifdef _UNICODE
    CStringA a(s);
    return std::string(a.GetString());
#else
    return std::string(s.GetString());
#endif
}
// ---- å®‰å…¨æ‹¼æŽ¥å·¥å…·ï¼šä¸ä½¿ç”¨è¿ç®—符 +(避免重载/转换问题)----
static std::string sjoin(std::initializer_list<std::string> parts) {
    std::string out;
    size_t total = 0; for (const auto& p : parts) total += p.size();
    out.reserve(total);
    for (const auto& p : parts) out.append(p);
    return out;
}
// ---- æ—¶é—´æ ¼å¼å·¥å…·ï¼šnow + days/minutes åç§» ----
static std::string _fmt_time(int daysOff, int minutesOff) {
    using namespace std::chrono;
    auto now = system_clock::now() + hours(24 * daysOff) + minutes(minutesOff);
    std::time_t tt = system_clock::to_time_t(now);
    std::tm tm{};
#ifdef _WIN32
    localtime_s(&tm, &tt);
#else
    tm = *std::localtime(&tt);
#endif
    char buf[32];
    std::snprintf(buf, sizeof(buf), "%04d-%02d-%02d %02d:%02d:%02d",
        tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
    return std::string(buf);
}
// ---- é€ å…¨é‡å‡æ•°æ®ï¼ˆå«åŒé¡µé…å¯¹/跨页配对/单条)----
static void _make_all_fake(std::vector<FakeDbRecord>& outAll) {
    outAll.clear();
    int id = 1000;
    // ===== å…ˆé€ å‡ æ¡â€œæ²¡æœ‰ Buddy”的记录(保证出现在第 1 é¡µå¼€å¤´ï¼‰=====
    for (int n = 1; n <= 4; ++n) {
        CString cid; cid.Format(_T("NB%03d"), n);
        outAll.push_back(FakeDbRecord{
            id++, 700 + n, 800 + n, toAnsi(cid),
            n % 3, n % 5,
            _fmt_time(0, -n * 5),           // å¼€å§‹æ—¶é—´ç¨æ—©ä¸€ç‚¹
            _fmt_time(0, -n * 5 + 1),
            std::string(),                // buddyId ä¸ºç©º
            n % 4,
            sjoin({ "path/", toAnsi(cid) }),
            sjoin({ "{\"noBuddy\":", std::to_string(n), "}" })
            });
    }
    // ===== é€ é…å¯¹æ•°æ®çš„工具 =====
    auto mkPair = [&](int k, bool crossPage) {
        // äº’为 buddy çš„两条
        CString a; a.Format(_T("G%04dA"), k);
        CString b; b.Format(_T("G%04dB"), k);
        FakeDbRecord A{
            id++, 100 + k, 200 + k, toAnsi(a),
            k % 3, k % 5, _fmt_time(0, k * 3), _fmt_time(0, k * 3 + 2),
            toAnsi(b), k % 4,
            sjoin({ "path/", toAnsi(a) }),
            sjoin({ "{\"k\":\"", toAnsi(a), "\"}" })
        };
        FakeDbRecord B{
            id++, 110 + k, 210 + k, toAnsi(b),
            (k + 1) % 3, (k + 2) % 5, _fmt_time(0, k * 3 + 1), _fmt_time(0, k * 3 + 4),
            toAnsi(a), (k + 1) % 4,
            sjoin({ "path/", toAnsi(b) }),
            sjoin({ "{\"k\":\"", toAnsi(b), "\"}" })
        };
        if (crossPage) {
            // å…ˆæ”¾ A,再插 3 æ¡â€œå•条”把 B æŒ¤åˆ°åŽé¡µï¼Œæœ€åŽæ”¾ B
            outAll.push_back(A);
            for (int s = 0; s < 3; ++s) {
                CString sid; sid.Format(_T("S%04d_%d"), k, s);
                outAll.push_back(FakeDbRecord{
                    id++, 300 + k * 10 + s, 400 + k * 10 + s, toAnsi(sid),
                    (k + s) % 3, (k + s) % 5, _fmt_time(0, k * 2 + s), _fmt_time(0, k * 2 + s + 1),
                    std::string(), (k + s) % 4,
                    sjoin({ "path/", toAnsi(sid) }),
                    sjoin({ "{\"single\":", std::to_string(s), "}" })
                    });
            }
            outAll.push_back(B);
        }
        else {
            // åŒé¡µç´§æŒ¨ç€
            outAll.push_back(A);
            outAll.push_back(B);
        }
    };
    // ===== ç„¶åŽæŒ‰åŽŸé€»è¾‘è¿½åŠ ï¼šåŒé¡µé…å¯¹ / è·¨é¡µé…å¯¹ / å•条 =====
    // 6 ç»„同页配对(12 æ¡ï¼‰
    for (int k = 1; k <= 6; ++k) mkPair(k, false);
    // 4 ç»„跨页配对(每组中间插 3 æ¡â€œå•条”)
    for (int k = 101; k <= 104; ++k) mkPair(k, true);
    // è‹¥å¹²â€œå•条”
    for (int u = 201; u < 806; ++u) {
        CString cid; cid.Format(_T("U%04d"), u);
        outAll.push_back(FakeDbRecord{
            id++, 500 + u, 600 + u, toAnsi(cid),
            u % 3, u % 5, _fmt_time(0, u % 17), _fmt_time(0, (u % 17) + 1),
            std::string(), u % 4,
            sjoin({ "path/", toAnsi(cid) }),
            sjoin({ "{\"u\":", std::to_string(u), "}" })
            });
    }
}
// ---- åšåˆ†é¡µåˆ‡ç‰‡ï¼ˆå¯æŒ‰éœ€åŠ æ›´ä¸¥æ ¼çš„ filters)----
static FakeDbPage _make_page_fake(const GlassLogDb::Filters& /*f*/, int pageSize, int offset) {
    std::vector<FakeDbRecord> all;
    _make_all_fake(all);
    FakeDbPage page;
    int n = (int)all.size();
    int beg = min(max(0, offset), n);
    int end = min(beg + max(0, pageSize), n);
    page.items.insert(page.items.end(), all.begin() + beg, all.begin() + end);
    return page;
}
static int _fake_total_count() {
    std::vector<FakeDbRecord> all;
    _make_all_fake(all);
    return (int)all.size();
}
#endif // USE_FAKE_DB_DEMO
// åˆ¤æ–­æŸ parent ä¸‹æ˜¯å¦å·²å­˜åœ¨ classId == cid çš„子节点(忽略大小写)
static bool NodeHasChildWithClassId(CExpandableListCtrl::Node* parent, const CString& cid)
{
    if (!parent) return false;
    for (auto& ch : parent->children) {
        if (ch && ch->cols.size() > 4) {
            if (ch->cols[4].CompareNoCase(cid) == 0)
                return true;
        }
    }
    return false;
}
// æŠŠ cols å†™å›žåˆ°æŸä¸€è¡Œï¼ˆä»Žç¬¬1列开始;第0列我们没用)
static void ApplyColsToRow(CListCtrl& lv, int row, const std::vector<CString>& cols) {
    int colCount = 0;
    if (auto* hdr = lv.GetHeaderCtrl()) colCount = hdr->GetItemCount();
    colCount = min(colCount, (int)cols.size());
    for (int c = 1; c < colCount; ++c) {
        lv.SetItemText(row, c, cols[c]);
    }
}
// é€‰ä¸­è¡Œçš„ ClassID å¿«ç…§ï¼ˆç”¨äºŽé‡å»ºåŽæ¢å¤ï¼‰
static std::unordered_set<std::string> SnapshotSelectedKeys(CListCtrl& lv) {
    std::unordered_set<std::string> keys;
    int n = lv.GetItemCount();
    for (int i = 0; i < n; ++i) {
        if ((lv.GetItemState(i, LVIS_SELECTED) & LVIS_SELECTED) == 0) continue;
        CString cls = lv.GetItemText(i, 4);
#ifdef _UNICODE
        keys.insert(CT2A(cls));
#else
        keys.insert(cls.GetString());
#endif
    }
    return keys;
}
// é¡¶è¡Œï¼ˆTopIndex)对应的 ClassID
static std::optional<std::string> SnapshotTopKey(CListCtrl& lv) {
    int top = lv.GetTopIndex();
    if (top < 0 || top >= lv.GetItemCount()) return std::nullopt;
    CString cls = lv.GetItemText(top, 4);
#ifdef _UNICODE
    return std::optional<std::string>(CT2A(cls));
#else
    return std::optional<std::string>(cls.GetString());
#endif
}
// ç”¨ ClassID é›†åˆæ¢å¤é€‰ä¸­
static void RestoreSelectionByKeys(CListCtrl& lv, const std::unordered_set<std::string>& keys) {
    int n = lv.GetItemCount();
    for (int i = 0; i < n; ++i) lv.SetItemState(i, 0, LVIS_SELECTED);
    for (int i = 0; i < n; ++i) {
        CString cls = lv.GetItemText(i, 4);
#ifdef _UNICODE
        if (keys.count(CT2A(cls))) lv.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
#else
        if (keys.count(cls.GetString())) lv.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
#endif
    }
}
// å°½é‡æŠŠæŸä¸ª ClassID æ»šå›žå¯è§
static void RestoreTopByKey(CListCtrl& lv, const std::optional<std::string>& key) {
    if (!key) return;
    int n = lv.GetItemCount();
    for (int i = 0; i < n; ++i) {
        CString cls = lv.GetItemText(i, 4);
#ifdef _UNICODE
        if (CT2A(cls) == *key) { lv.EnsureVisible(i, FALSE); break; }
#else
        if (cls.GetString() == *key) { lv.EnsureVisible(i, FALSE); break; }
#endif
    }
}
// CPageGlassList å¯¹è¯æ¡†
@@ -23,9 +352,8 @@
    m_pObserver = nullptr;
    m_strStatus = "";
    m_strKeyword = "";
    m_nCurPage = 0;
    m_nTotalPages = 0;
    m_nCurPage = 1;
    m_nTotalPages = 1;
    memset(m_szTimeStart, 0, sizeof(m_szTimeStart));
    memset(m_szTimeEnd, 0, sizeof(m_szTimeEnd));
@@ -37,6 +365,7 @@
{
    if (m_hbrBkgnd != nullptr) {
        ::DeleteObject(m_hbrBkgnd);
        m_hbrBkgnd = nullptr;
    }
    if (m_pObserver != nullptr) {
        m_pObserver->unsubscribe();
@@ -52,7 +381,6 @@
    DDX_Control(pDX, IDC_LIST_ALARM, m_listCtrl);
}
BEGIN_MESSAGE_MAP(CPageGlassList, CDialogEx)
    ON_WM_CTLCOLOR()
    ON_WM_DESTROY()
@@ -66,11 +394,16 @@
    ON_BN_CLICKED(IDC_BUTTON_NEXT_PAGE, &CPageGlassList::OnBnClickedButtonNextPage)
END_MESSAGE_MAP()
// ===== ç§æœ‰å°å·¥å…· =====
static int GetColumnCount(CListCtrl& lv)
{
    CHeaderCtrl* pHdr = lv.GetHeaderCtrl();
    return pHdr ? pHdr->GetItemCount() : 0;
}
// CPageGlassList æ¶ˆæ¯å¤„理程序
// ===== CPageGlassList æ¶ˆæ¯å¤„理程序 =====
void CPageGlassList::InitRxWindow()
{
    /* code */
    // è®¢é˜…数据
    IRxWindows* pRxWindows = RX_GetRxWindows();
    pRxWindows->enableLog(5);
@@ -81,14 +414,14 @@
            int code = pAny->getCode();
            if (RX_CODE_EQ_ROBOT_TASK == code) {
                UpdatePageData();
                UpdateWipData();   // åªæ›´æ–°ï¼Œä¸é‡å»ºï¼Œä¸æ”¹å˜å±•å¼€/选择
            }
            pAny->release();
            }, [&]() -> void {
                // onComplete
            }, [&](IThrowable* pThrowable) -> void {
                // onErrorm
                // onError
                pThrowable->printf();
            });
@@ -120,16 +453,6 @@
    CWnd* pLabelPage = GetDlgItem(IDC_LABEL_PAGE_NUMBER);
    if (pBtnNext && pBtnPrev && pLabelPage) {
        // èŽ·å–åˆ†é¡µæ–‡æœ¬å®½åº¦ä¼°ç®—
        //CString strLabel;
        //GetDlgItemText(IDC_LABEL_PAGE_NUMBER, strLabel);
        //if (strLabel.IsEmpty()) {
        //    strLabel = _T("第 1 / 1 é¡µ");
        //}
        //int nCharWidth = 8;
        //int nLabelWidth = strLabel.GetLength() * nCharWidth + 20;
        // è®¾ç½®æŒ‰é’®å’Œæ ‡ç­¾ä½ç½®
        pBtnNext->MoveWindow(xRight - nButtonWidth, yBottom, nButtonWidth, nButtonHeight);
        xRight -= nButtonWidth + nSpacing;
@@ -181,24 +504,12 @@
    if (m_dateTimeStart.m_hWnd == nullptr || m_dateTimeEnd.m_hWnd == nullptr) {
        return;
    }
    // ç¦ç”¨åˆå§‹çŠ¶æ€
    // è‡ªå®šä¹‰èŒƒå›´æ—¶æ‰å¯ç¼–辑
    m_dateTimeStart.EnableWindow(FALSE);
    m_dateTimeEnd.EnableWindow(FALSE);
    // è®¾ç½®æ ¼å¼ï¼šæ˜¾ç¤ºæ—¥æœŸ + æ—¶é—´
    //m_dateTimeStart.SetFormat(_T("yyyy/MM/dd HH:mm:ss"));
    //m_dateTimeEnd.SetFormat(_T("yyyy/MM/dd HH:mm:ss"));
    // ä¿®æ”¹æ ·å¼ä»¥æ”¯æŒæ—¶é—´æ ¼å¼
    //DWORD dwStyleStart = m_dateTimeStart.GetStyle();
    //DWORD dwStyleEnd = m_dateTimeEnd.GetStyle();
    //m_dateTimeStart.ModifyStyle(0, DTS_TIMEFORMAT | DTS_UPDOWN);
    //m_dateTimeEnd.ModifyStyle(0, DTS_TIMEFORMAT);
}
void CPageGlassList::LoadTransfers()
void CPageGlassList::LoadData()
{
    m_nCurPage = 1;
    UpdatePageData();
@@ -206,19 +517,214 @@
void CPageGlassList::UpdatePageData()
{
    /*
    TransferData filter;
    filter.strStatus = m_strStatus;
    filter.strDescription = m_strKeyword;
    filter.strCreateTime = m_szTimeStart;
    filter.strEndTime = m_szTimeEnd;
    auto vecData = TransferManager::getInstance().getTransfers(filter, m_nCurPage, PAGE_SIZE);
    FillDataToListCtrl(vecData);
    m_rebuilding = true;
    int nTotalRecords = TransferManager::getInstance().getFilteredTransferCount(filter);
    m_nTotalPages = (nTotalRecords + PAGE_SIZE - 1) / PAGE_SIZE;
    // æ”¾åœ¨ä»»ä½•清空/重建动作之前:
    auto expandedKeys = SnapshotExpandedKeys(m_listCtrl);
    // â€”— åŒä¿é™©ï¼šå…ˆæ¸…掉可见项,再清树结构 â€”—
    m_listCtrl.SetRedraw(FALSE);
    m_listCtrl.DeleteAllItems();
    m_listCtrl.SetRedraw(TRUE);
    // â€”— æ¸…空树(依赖 CExpandableListCtrl::ClearTree())——
    m_listCtrl.ClearTree();
    const int colCount = m_listCtrl.GetHeaderCtrl() ? m_listCtrl.GetHeaderCtrl()->GetItemCount() : 0;
    if (colCount <= 0) return;
    // ==================== 1) WIP:仅第 1 é¡µæž„建,且放在最顶部 ====================
    if (m_nCurPage == 1) {
        std::vector<SERVO::CGlass*> wipGlasses;
        theApp.m_model.m_master.getWipGlasses(wipGlasses);
        std::vector<SERVO::CGlass*> tempGlasses = wipGlasses; // å¾…释放
        auto glassHit = [&](SERVO::CGlass* g) -> bool {
            return g && GlassMatchesFilters(*g, m_filters);
        };
        std::unordered_set<SERVO::CGlass*> usedWip;
        for (auto* g : wipGlasses) {
            if (!glassHit(g) || usedWip.count(g)) continue;
            SERVO::CGlass* b = g->getBuddy();
            if (b) {
                SERVO::CGlass* parent = g;
                SERVO::CGlass* child = b;
                std::vector<CString> pcols(colCount);
                pcols[1] = _T("");
                pcols[2] = std::to_string(parent->getCassetteSequenceNo()).c_str();
                pcols[3] = std::to_string(parent->getJobSequenceNo()).c_str();
                pcols[4] = parent->getID().c_str();
                pcols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText(parent->getType()).c_str();
                pcols[6] = SERVO::CServoUtilsTool::getGlassStateText(parent->state()).c_str();
                pcols[7] = CToolUnits::TimePointToLocalString(parent->tStart()).c_str();
                pcols[8] = CToolUnits::TimePointToLocalString(parent->tEnd()).c_str();
                pcols[9] = parent->getBuddyId().c_str();
                pcols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)parent->getAOIInspResult()).c_str();
                pcols[11] = parent->getPathDescription().c_str();
                pcols[12] = parent->getParamsDescription().c_str();
                auto* nParent = m_listCtrl.InsertRoot(pcols);
                MaybeRestoreExpandByKey(nParent, expandedKeys);
                m_listCtrl.SetNodeColor(nParent, kWipText, kWipParentBk);   // çˆ¶ï¼šåŸºç¡€ç»¿
                std::vector<CString> ccols(colCount);
                ccols[1] = _T("");
                ccols[2] = std::to_string(child->getCassetteSequenceNo()).c_str();
                ccols[3] = std::to_string(child->getJobSequenceNo()).c_str();
                ccols[4] = child->getID().c_str();
                ccols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText(child->getType()).c_str();
                ccols[6] = SERVO::CServoUtilsTool::getGlassStateText(child->state()).c_str();
                ccols[7] = CToolUnits::TimePointToLocalString(child->tStart()).c_str();
                ccols[8] = CToolUnits::TimePointToLocalString(child->tEnd()).c_str();
                ccols[9] = child->getBuddyId().c_str();
                ccols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)child->getAOIInspResult()).c_str();
                ccols[11] = child->getPathDescription().c_str();
                ccols[12] = child->getParamsDescription().c_str();
                auto* nChild = m_listCtrl.InsertChild(nParent, ccols);
                m_listCtrl.SetNodeColor(nChild, kWipText, kWipChildBk);     // å­ï¼šæ›´æµ…
                usedWip.insert(parent);
                usedWip.insert(child);
            }
            else {
                std::vector<CString> cols(colCount);
                cols[1] = _T("");
                cols[2] = std::to_string(g->getCassetteSequenceNo()).c_str();
                cols[3] = std::to_string(g->getJobSequenceNo()).c_str();
                cols[4] = g->getID().c_str();
                cols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText(g->getType()).c_str();
                cols[6] = SERVO::CServoUtilsTool::getGlassStateText(g->state()).c_str();
                cols[7] = CToolUnits::TimePointToLocalString(g->tStart()).c_str();
                cols[8] = CToolUnits::TimePointToLocalString(g->tEnd()).c_str();
                cols[9] = g->getBuddyId().c_str();
                cols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)g->getAOIInspResult()).c_str();
                cols[11] = g->getPathDescription().c_str();
                cols[12] = g->getParamsDescription().c_str();
                auto* n = m_listCtrl.InsertRoot(cols);
                m_listCtrl.SetNodeColor(n, kWipText, kWipParentBk);         // ä»ç”¨åŸºç¡€ç»¿
                usedWip.insert(g);
            }
        }
        for (auto* item : tempGlasses) item->release();
    }
    // ==================== 2) DB å½“前页(无论第几页都构建;排在 WIP ä¹‹åŽï¼‰ ====================
#if USE_FAKE_DB_DEMO
    auto page = _make_page_fake(m_filters, PAGE_SIZE, PAGE_SIZE * (m_nCurPage - 1));
#else
    auto& db = GlassLogDb::Instance();
    auto page = db.queryPaged(m_filters, PAGE_SIZE, PAGE_SIZE * (m_nCurPage - 1));
#endif
    std::unordered_map<std::string, size_t> idxById;
    idxById.reserve(page.items.size());
    for (size_t i = 0; i < page.items.size(); ++i) idxById[page.items[i].classId] = i;
    std::unordered_set<std::string> usedDb;
    int zebra = 0;
    for (size_t i = 0; i < page.items.size(); ++i) {
        const auto& r = page.items[i];
        if (usedDb.count(r.classId)) continue;
        COLORREF bk = (zebra % 2 == 0) ? RGB(255, 255, 255) : RGB(235, 235, 235);
        bool paired = false;
        if (!r.buddyId.empty()) {
            auto it = idxById.find(r.buddyId);
            if (it != idxById.end()) {
                const auto& br = page.items[it->second];
                if (!usedDb.count(br.classId)) {
                    bool rIsParent = (r.classId <= br.classId);
                    const auto& parentRec = rIsParent ? r : br;
                    const auto& childRec = rIsParent ? br : r;
                    std::vector<CString> pcols(colCount);
                    pcols[1] = std::to_string(parentRec.id).c_str(); pcols[2] = std::to_string(parentRec.cassetteSeqNo).c_str();
                    pcols[3] = std::to_string(parentRec.jobSeqNo).c_str(); pcols[4] = parentRec.classId.c_str();
                    pcols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)parentRec.materialType).c_str();
                    pcols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)parentRec.state).c_str();
                    pcols[7] = parentRec.tStart.c_str(); pcols[8] = parentRec.tEnd.c_str(); pcols[9] = parentRec.buddyId.c_str();
                    pcols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)parentRec.aoiResult).c_str();
                    pcols[11] = parentRec.path.c_str(); pcols[12] = parentRec.params.c_str();
                    auto* nParent = m_listCtrl.InsertRoot(pcols);
                    MaybeRestoreExpandByKey(nParent, expandedKeys);
                    m_listCtrl.SetNodeColor(nParent, RGB(0, 0, 0), bk);
                    std::vector<CString> ccols(colCount);
                    ccols[1] = std::to_string(childRec.id).c_str(); ccols[2] = std::to_string(childRec.cassetteSeqNo).c_str();
                    ccols[3] = std::to_string(childRec.jobSeqNo).c_str(); ccols[4] = childRec.classId.c_str();
                    ccols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)childRec.materialType).c_str();
                    ccols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)childRec.state).c_str();
                    ccols[7] = childRec.tStart.c_str(); ccols[8] = childRec.tEnd.c_str(); ccols[9] = childRec.buddyId.c_str();
                    ccols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)childRec.aoiResult).c_str();
                    ccols[11] = childRec.path.c_str(); ccols[12] = childRec.params.c_str();
                    auto* nChild = m_listCtrl.InsertChild(nParent, ccols);
                    m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), bk);
                    usedDb.insert(parentRec.classId);
                    usedDb.insert(childRec.classId);
                    paired = true;
                }
            }
        }
        if (!paired && !r.buddyId.empty()) {
            std::vector<CString> pcols(colCount);
            pcols[1] = std::to_string(r.id).c_str(); pcols[2] = std::to_string(r.cassetteSeqNo).c_str();
            pcols[3] = std::to_string(r.jobSeqNo).c_str(); pcols[4] = r.classId.c_str();
            pcols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str();
            pcols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str();
            pcols[7] = r.tStart.c_str(); pcols[8] = r.tEnd.c_str(); pcols[9] = r.buddyId.c_str();
            pcols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str();
            pcols[11] = r.path.c_str(); pcols[12] = r.params.c_str();
            auto* nParent = m_listCtrl.InsertRoot(pcols);
            MaybeRestoreExpandByKey(nParent, expandedKeys);
            m_listCtrl.SetNodeColor(nParent, RGB(0, 0, 0), bk);
            std::vector<CString> ccols(colCount);
            ccols[4] = r.buddyId.c_str(); // å ä½å­è¡Œï¼šæ˜¾ç¤º buddy çš„ classId
            auto* nChild = m_listCtrl.InsertChild(nParent, ccols);
            m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), bk);
            usedDb.insert(r.classId);
            paired = true;
        }
        if (!paired) {
            std::vector<CString> cols(colCount);
            cols[1] = std::to_string(r.id).c_str(); cols[2] = std::to_string(r.cassetteSeqNo).c_str();
            cols[3] = std::to_string(r.jobSeqNo).c_str(); cols[4] = r.classId.c_str();
            cols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str();
            cols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str();
            cols[7] = r.tStart.c_str(); cols[8] = r.tEnd.c_str(); cols[9] = r.buddyId.c_str();
            cols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str();
            cols[11] = r.path.c_str(); cols[12] = r.params.c_str();
            auto* n = m_listCtrl.InsertRoot(cols);
            m_listCtrl.SetNodeColor(n, RGB(0, 0, 0), bk);
            usedDb.insert(r.classId);
        }
        ++zebra;
    }
    // ä¸€æ¬¡æ€§é‡ç»˜
    m_listCtrl.RebuildVisible();
    // ä¸Šä¸€é¡µ / ä¸‹ä¸€é¡µ
    UpdatePageControls();
    */
    m_rebuilding = false;
}
void CPageGlassList::UpdatePageControls()
@@ -228,99 +734,15 @@
    SetDlgItemText(IDC_LABEL_PAGE_NUMBER, strPage);
    GetDlgItem(IDC_BUTTON_PREV_PAGE)->EnableWindow(m_nCurPage > 1);
    GetDlgItem(IDC_BUTTON_NEXT_PAGE)->EnableWindow(m_nCurPage < m_nTotalPages);
    Resize();
}
void CPageGlassList::UpdateDateFilter()
{
    CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_DATETIME);
    if (nullptr != pComboBox) {
        int nIndex = pComboBox->GetCurSel();
        if (nIndex == 0) {
            memset(m_szTimeStart, 0, sizeof(m_szTimeStart));
            memset(m_szTimeEnd, 0, sizeof(m_szTimeEnd));
            m_szTimeStart[0] = '\0';
            m_szTimeEnd[0] = '\0';
        }
        else {
            CTime time = CTime::GetCurrentTime();
            if (nIndex == 1) {
                sprintf_s(m_szTimeStart, 64, "%d-%02d-%02d 00:00:00", time.GetYear(), time.GetMonth(), time.GetDay());
                sprintf_s(m_szTimeEnd, 64, "%d-%02d-%02d 23:59:59", time.GetYear(), time.GetMonth(), time.GetDay());
            }
            else if (nIndex == 2) {
                CTime time2 = time - CTimeSpan(7, 0, 0, 0);
                sprintf_s(m_szTimeStart, 64, "%d-%02d-%02d 00:00:00", time2.GetYear(), time2.GetMonth(), time2.GetDay());
                sprintf_s(m_szTimeEnd, 64, "%d-%02d-%02d 23:59:59", time.GetYear(), time.GetMonth(), time.GetDay());
            }
            else if (nIndex == 3) {
                sprintf_s(m_szTimeStart, 64, "%d-%02d-01 00:00:00", time.GetYear(), time.GetMonth());
                sprintf_s(m_szTimeEnd, 64, "%d-%02d-%02d 23:59:59", time.GetYear(), time.GetMonth(), time.GetDay());
            }
            else if (nIndex == 4) {
                sprintf_s(m_szTimeStart, 64, "%d-01-01 00:00:00", time.GetYear());
                sprintf_s(m_szTimeEnd, 64, "%d-12-31 23:59:59", time.GetYear());
            }
            else if (nIndex == 5) {
                SYSTEMTIME t1, t2;
                m_dateTimeStart.GetTime(&t1);
                m_dateTimeEnd.GetTime(&t2);
                //sprintf_s(m_szTimeStart, 64, "%d-%02d-%02d %02d:%02d:%02d", t1.wYear, t1.wMonth, t1.wDay, t1.wHour, t1.wMinute, t1.wSecond);
                //sprintf_s(m_szTimeEnd, 64, "%d-%02d-%02d %02d:%02d:%02d", t2.wYear, t2.wMonth, t2.wDay, t2.wHour, t2.wMinute, t2.wSecond);
                sprintf_s(m_szTimeStart, 64, "%d-%02d-%02d 00:00:00", t1.wYear, t1.wMonth, t1.wDay);
                sprintf_s(m_szTimeEnd, 64, "%d-%02d-%02d 23:59:59", t2.wYear, t2.wMonth, t2.wDay);
            }
        }
    }
}
/*
void CPageGlassList::FillDataToListCtrl(const std::vector<TransferData>& vecData)
{
    if (m_listCtrl.m_hWnd == nullptr) {
        return;
    }
    m_listCtrl.DeleteAllItems();
    for (const auto& item : vecData) {
        InsertTransferData(item);
    }
}
void CPageGlassList::InsertTransferData(const TransferData& data)
{
    if (m_listCtrl.m_hWnd == nullptr) {
        return;
    }
    int nIndex = m_listCtrl.GetItemCount();
    if (nIndex < 0) {
        return;
    }
    int nItem = m_listCtrl.InsertItem(nIndex, _T(""));
    CString str;
    str.Format(_T("%d"), data.nRecordId);
    m_listCtrl.SetItemText(nItem, 1, str);
    m_listCtrl.SetItemText(nItem, 2, CString(data.strStatus.c_str()));
    m_listCtrl.SetItemText(nItem, 3, CString(data.strClassID.c_str()));
    m_listCtrl.SetItemText(nItem, 4, CString(data.strCreateTime.c_str()));
    m_listCtrl.SetItemText(nItem, 5, CString(data.strPickTime.c_str()));
    m_listCtrl.SetItemText(nItem, 6, CString(data.strPlaceTime.c_str()));
    m_listCtrl.SetItemText(nItem, 7, CString(data.strEndTime.c_str()));
    m_listCtrl.SetItemText(nItem, 8, CString(data.strDescription.c_str()));
}
*/
// CPageTransferLog æ¶ˆæ¯å¤„理程序
BOOL CPageGlassList::OnInitDialog()
{
    CDialogEx::OnInitDialog();
    // TODO:  åœ¨æ­¤æ·»åŠ é¢å¤–çš„åˆå§‹åŒ–
    // å®šæ—¶å™¨ï¼š1=初始化订阅,2=周期刷新(只增量)
    SetTimer(1, 3000, nullptr);
    SetTimer(2, 2000, nullptr);
    // ä¸‹æ‹‰æ¡†æŽ§ä»¶
    InitStatusCombo();
@@ -336,6 +758,7 @@
    DWORD dwStyle = m_listCtrl.GetExtendedStyle();
    dwStyle |= LVS_EX_FULLROWSELECT;
    dwStyle |= LVS_EX_GRIDLINES;
    dwStyle |= LVS_EX_DOUBLEBUFFER;
    m_listCtrl.SetExtendedStyle(dwStyle);
    HIMAGELIST imageList = ImageList_Create(24, 24, ILC_COLOR24, 1, 1);
@@ -343,6 +766,7 @@
    CString headers[] = { 
        _T(""), 
        _T("id"),
        _T("Cassette Sequence No"),
        _T("Job Sequence No"),
        _T("Class ID"),
@@ -355,27 +779,21 @@
        _T("路径"),
        _T("工艺参数") 
    };
    int widths[] = { 0, 80, 80, 100, 120, 120, 120, 120, 200, 200, 200, 200 };
    int widths[] = { 24, 80, 80, 80, 100, 120, 120, 120, 120, 200, 200, 200, 200 };
    for (int i = 0; i < _countof(headers); ++i) {
        strItem.Format(_T("Col_%d_Width"), i);
        widths[i] = GetPrivateProfileInt("GlassListCtrl", strItem, widths[i], strIniFile);
        m_listCtrl.InsertColumn(i, headers[i], LVCFMT_LEFT, widths[i]);
        int def = widths[i];
        widths[i] = GetPrivateProfileInt("GlassListCtrl", strItem, def, strIniFile);
        if (i == 0 && widths[i] < 16) widths[i] = 24; // è®©ä¸‰è§’图标有空间展示
        m_listCtrl.InsertColumn(i, headers[i], i == 0 ? LVCFMT_RIGHT : LVCFMT_LEFT, widths[i]);
    }
    m_listCtrl.SetColumnWidth(10, LVSCW_AUTOSIZE_USEHEADER);
    // è®¡ç®—总页数
    /*
    int nTotalRecords = TransferManager::getInstance().getTotalTransferCountAll();
    m_nTotalPages = (nTotalRecords + PAGE_SIZE - 1) / PAGE_SIZE;
    m_nCurPage = 1;
    */
    // äºŒæ¬¡å…œåº•,防止 ini å†™è¿›äº† 0
    if (m_listCtrl.GetColumnWidth(0) < 16) m_listCtrl.SetColumnWidth(0, 24);
    Resize();
    LoadTransfers();
    OnBnClickedButtonSearch(); // è§¦å‘一次查询与首屏填充
    return TRUE;  // return TRUE unless you set the focus to a control
    // å¼‚常: OCX å±žæ€§é¡µåº”返回 FALSE
}
HBRUSH CPageGlassList::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
@@ -383,11 +801,9 @@
    if (nCtlColor == CTLCOLOR_STATIC) {
        pDC->SetBkColor(m_crBkgnd);
    }
    if (m_hbrBkgnd == nullptr) {
        m_hbrBkgnd = CreateSolidBrush(m_crBkgnd);
    }
    return m_hbrBkgnd;
}
@@ -403,16 +819,20 @@
        m_pObserver = nullptr;
    }
    // ä¿å­˜åˆ—宽
    // ä¿å­˜åˆ—宽(首列兜底,避免把 0 å†™å›žåŽ»ï¼‰
    CString strIniFile, strItem, strTemp;
    strIniFile.Format(_T("%s\\configuration.ini"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir);
    CHeaderCtrl* pHeader = m_listCtrl.GetHeaderCtrl();
    if (pHeader) {
    for (int i = 0; i < pHeader->GetItemCount(); i++) {
        RECT rect;
        pHeader->GetItemRect(i, &rect);
        strItem.Format(_T("Col_%d_Width"), i);
        strTemp.Format(_T("%d"), rect.right - rect.left);
            int w = rect.right - rect.left;
            if (i == 0 && w < 16) w = 24;
            strTemp.Format(_T("%d"), w);
        WritePrivateProfileString("GlassListCtrl", strItem, strTemp, strIniFile);
        }
    }
}
@@ -428,6 +848,10 @@
        KillTimer(1);
        InitRxWindow();
    }
    else if (nIDEvent == 2) {
        UpdateWipData();  // åªåšå¢žé‡ï¼Œä¸é‡å»º
    }
    CDialogEx::OnTimer(nIDEvent);
}
@@ -438,10 +862,6 @@
    int nCount = pComboBox->GetCount();
    m_dateTimeStart.EnableWindow(nIndex == nCount - 1);
    m_dateTimeEnd.EnableWindow(nIndex == nCount - 1);
    // æ›´æ–°æ—¥æœŸè¿‡æ»¤å™¨å’Œé¡µé¢æ•°æ®
    UpdateDateFilter();
    LoadTransfers();
}
void CPageGlassList::OnCbnSelchangeComboStatusFilter()
@@ -456,7 +876,6 @@
        pComboBox->GetLBText(nIndex, cstrText);
        m_strStatus = CT2A(cstrText);
    }
    LoadTransfers();
}
void CPageGlassList::OnBnClickedButtonSearch()
@@ -464,11 +883,51 @@
    // èŽ·å–å…³é”®å­—è¾“å…¥æ¡†å†…å®¹
    CString strKeyword;
    GetDlgItemText(IDC_EDIT_KEYWORD, strKeyword);
    m_strKeyword = CT2A(strKeyword);
    m_filters.keyword = CT2A(strKeyword);
    // æ›´æ–°æ—¥æœŸè¿‡æ»¤å™¨å’Œé¡µé¢æ•°æ®
    UpdateDateFilter();
    LoadTransfers();
    CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_DATETIME);
    int index = pComboBox->GetCurSel();
    if (index == 0) {
        // ä¸é™
        m_filters.tStartFrom = std::nullopt;
        m_filters.tStartTo = std::nullopt;
    }
    else if (index == 1) {
        auto [fromUtc, toUtc] = CToolUnits::CalcQuickRangeUtc(QuickRange::Today);
        m_filters.tStartFrom = fromUtc;
        m_filters.tStartTo = toUtc;
    }
    else if (index == 2) {
        auto [fromUtc, toUtc] = CToolUnits::CalcQuickRangeUtc(QuickRange::Last7Days);
        m_filters.tStartFrom = fromUtc;
        m_filters.tStartTo = toUtc;
    }
    else if (index == 3) {
        auto [fromUtc, toUtc] = CToolUnits::CalcQuickRangeUtc(QuickRange::ThisMonth);
        m_filters.tStartFrom = fromUtc;
        m_filters.tStartTo = toUtc;
    }
    else if (index == 4) {
        auto [fromUtc, toUtc] = CToolUnits::CalcQuickRangeUtc(QuickRange::ThisYear);
        m_filters.tStartFrom = fromUtc;
        m_filters.tStartTo = toUtc;
    }
    else if (index == 5) {
        // è‡ªå®šä¹‰
        std::chrono::system_clock::time_point tp;
        if (CToolUnits::GetCtrlDateRangeUtc_StartOfDay(m_dateTimeStart, tp)) m_filters.tStartFrom = tp;
        if (CToolUnits::GetCtrlDateRangeUtc_EndOfDay(m_dateTimeEnd, tp))   m_filters.tStartTo = tp;
    }
#if USE_FAKE_DB_DEMO
    long long total = _fake_total_count();
#else
    auto& db = GlassLogDb::Instance();
    long long total = db.count(m_filters);
#endif
    m_nTotalPages = (PAGE_SIZE > 0) ? int((total + PAGE_SIZE - 1) / PAGE_SIZE) : 1;
    LoadData();
}
void CPageGlassList::OnBnClickedButtonExport()
@@ -478,90 +937,20 @@
        return;
    }
    CStdioFile file;
    if (!file.Open(fileDialog.GetPathName(), CFile::modeCreate | CFile::modeWrite | CFile::typeText)) {
        AfxMessageBox(_T("创建文件失败!"));
        return;
    // å¯¼å‡º CSV:导出符合 filters çš„“全部记录”(不受分页限制)
    auto& db = GlassLogDb::Instance();
    std::string csvPath((LPTSTR)(LPCTSTR)fileDialog.GetPathName());
    if (db.exportCsv(csvPath, m_filters) > 0) {
        AfxMessageBox("导出CSV成功!");
    }
    CString strHeader = _T("任务ID,状态,ClassID,创建时间,取片时间,放片时间,结束时间,描述\n");
    file.WriteString(strHeader);
    for (int i = 0; i < m_listCtrl.GetItemCount(); ++i) {
        CString row;
        for (int j = 1; j <= 8; ++j) {
            row += m_listCtrl.GetItemText(i, j);
            if (j != 8) {
                row += ",";
            }
        }
        row += "\n";
        file.WriteString(row);
    }
    file.Close();
}
void CPageGlassList::OnBnClickedButtonPrevPage()
{
    SERVO::CGlass g;
    g.setID("GLS-001");
    g.setType(SERVO::MaterialsType::G1);
    g.setOriginPort(1, 5);
    g.setScheduledForProcessing(TRUE);
    g.m_failReason = "none";
    g.markQueued();
    g.markStart();
    // æ·»åŠ å‚æ•°
    CParam p1("校正对位延时", "P001", "ms", 123);
    CParam p2("温度", "P002", "degC", 25.5);
    g.getParams().push_back(p1);
    g.getParams().push_back(p2);
    // è®¾ç½® JobDataS
    SERVO::CJobDataS* js = g.getJobDataS();
    js->setCassetteSequenceNo(10);
    js->setJobSequenceNo(20);
    js->setLotId("LOT-ABC");
    js->setGlass1Id("GLS-001");
    // æ·»åŠ  Path
    g.addPath(100, 1);
    SERVO::CPath* tail = g.getPath()->getTailPath();
    tail->setInTime(111111);
    tail->setOutTime(222222);
    tail->setInspResult(SERVO::InspResult::Pass);
    tail->processEnd();
    return;
    // 2. è½¬ä¸º JSON
    std::string jsonText = GlassJson::ToPrettyString(g);
    TRACE("序列化结果:\n%s\n\n", jsonText.c_str());
    // 3. ååºåˆ—化
    SERVO::CGlass g2;
    std::string err;
    if (!GlassJson::FromString(jsonText, g2, &err)) {
        TRACE("解析失败: %s\n", err.c_str());
        return;
    }
    // 4. æ‰“印验证
    TRACE("反序列化后的ID: %s\n", g2.getID().c_str());
    TRACE("反序列化后的参数数量: %d\n", (int)g2.getParams().size());
    if (!g2.getParams().empty()) {
        TRACE("第一个参数名: %s å€¼=%d\n",
            g2.getParams()[0].getName().c_str(),
            g2.getParams()[0].getIntValue());
    }
    if (m_nCurPage > 1) {
        m_nCurPage--;
        UpdatePageData();
    }
}
void CPageGlassList::OnBnClickedButtonNextPage()
@@ -571,3 +960,290 @@
        UpdatePageData();
    }
}
void CPageGlassList::UpdateWipData()
{
    // åªåœ¨ç¬¬ 1 é¡µåˆ·æ–° WIP;其它页不动
    if (m_nCurPage != 1) return;
    const int colCount = m_listCtrl.GetHeaderCtrl() ? m_listCtrl.GetHeaderCtrl()->GetItemCount() : 0;
    if (colCount <= 0) return;
    // 1) æ”¶é›†å½“前可见里的“WIP è¡Œâ€ï¼ˆç¬¬1列 id ä¸ºç©ºï¼‰
    //    a) wipRowById:classId -> (row, node*),收集“根+子”的全部,便于判断“buddy æ˜¯å¦å·²åœ¨å¯è§è¡¨ä¸­â€
    //    b) wipRootById:classId -> node*,仅收集“根节点”,便于只对根节点补子项
    std::unordered_map<std::string, std::pair<int, CExpandableListCtrl::Node*>> wipRowById;
    std::unordered_map<std::string, CExpandableListCtrl::Node*> wipRootById;
    for (int row = 0; row < m_listCtrl.GetItemCount(); ++row) {
        CString idDb = m_listCtrl.GetItemText(row, 1); // ç¬¬1列是 DB id
        if (!idDb.IsEmpty()) continue;                 // æœ‰ id çš„æ˜¯ DB è¡Œï¼Œè·³è¿‡
        auto* node = m_listCtrl.GetNodeByVisibleIndex(row);
        CString cls = m_listCtrl.GetItemText(row, 4);  // ç¬¬4列 Class ID
#ifdef _UNICODE
        std::string key = CT2A(cls);
#else
        std::string key = cls.GetString();
#endif
        wipRowById[key] = { row, node };
        if (node && node->parent == nullptr) {
            wipRootById[key] = node;                   // ä»…根节点进入这个表
        }
    }
    // 2) æ‹‰å½“前 WIP åˆ—表
    std::vector<SERVO::CGlass*> wipGlasses;
    theApp.m_model.m_master.getWipGlasses(wipGlasses);
    std::vector<SERVO::CGlass*> tempRetain = wipGlasses; // ç¨åŽç»Ÿä¸€ release
    /*
    static int i = 0;
    i++;
    if (i == 8) {
        for (auto item : wipGlasses) {
            if (item->getBuddy() != nullptr) {
                item->setInspResult(EQ_ID_MEASUREMENT, 0, SERVO::InspResult::Fail);
                item->getBuddy()->setID("11111");
            }
        }
    }
    if (i == 16) {
        for (auto item : wipGlasses) {
            if (item->getBuddy() != nullptr) {
                item->setInspResult(EQ_ID_MEASUREMENT, 0, SERVO::InspResult::Pass);
                item->getBuddy()->setID("22222");
            }
        }
    }
    */
    auto makeColsFromWip = [&](SERVO::CGlass* g) {
        std::vector<CString> cols(colCount);
        cols[1] = _T(""); // WIP æ²¡ DB id
        cols[2] = std::to_string(g->getCassetteSequenceNo()).c_str();
        cols[3] = std::to_string(g->getJobSequenceNo()).c_str();
        cols[4] = g->getID().c_str();
        cols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText(g->getType()).c_str();
        cols[6] = SERVO::CServoUtilsTool::getGlassStateText(g->state()).c_str();
        cols[7] = CToolUnits::TimePointToLocalString(g->tStart()).c_str();
        cols[8] = CToolUnits::TimePointToLocalString(g->tEnd()).c_str();
        cols[9] = g->getBuddyId().c_str();
        cols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)g->getAOIInspResult()).c_str();
        cols[11] = g->getPathDescription().c_str();
        cols[12] = g->getParamsDescription().c_str();
        return cols;
    };
    bool needRebuildChildren = false;     // æœ¬æ¬¡æ˜¯å¦æ–°å¢žäº†å­èŠ‚ç‚¹ï¼ˆç»“æž„å˜åŒ–ï¼‰
    bool needRebuildAllForNewRoot = false;// æœ¬æ¬¡æ˜¯å¦å‘现了“新增根节点”的需求(为保证 WIP åœ¨é¡¶éƒ¨ï¼‰
    std::vector<int> rowsToRedraw;        // ä»…文本变化的行
    // UI çŠ¶æ€ï¼ˆå½“éœ€è¦é‡å»ºæ—¶ä½¿ç”¨ï¼‰
    std::vector<CExpandableListCtrl::Node*> savedSel;
    CExpandableListCtrl::Node* savedTop = nullptr;
    // 3) é€ä¸ªå¤„理 WIP:已存在 -> å°±åœ°æ›´æ–°ï¼›å¿…要时“只对根补子项”
    //                 ä¸å­˜åœ¨ -> è§¦å‘“全量重建”,以保证新 WIP æ ¹è¡Œå‡ºçŽ°åœ¨åˆ—è¡¨é¡¶éƒ¨
    for (auto* g : wipGlasses) {
        if (!GlassMatchesFilters(*g, m_filters)) continue;
#ifdef _UNICODE
        std::string cid = CT2A(g->getID().c_str());
#else
        std::string cid = g->getID();
#endif
        auto itAny = wipRowById.find(cid);
        if (itAny != wipRowById.end()) {
            // (A) å·²å­˜åœ¨ï¼šä»…更新文案 & é‡ç»˜è¯¥è¡Œ
            int row = itAny->second.first;
            m_listCtrl.SetItemText(row, 2, std::to_string(g->getCassetteSequenceNo()).c_str());
            m_listCtrl.SetItemText(row, 3, std::to_string(g->getJobSequenceNo()).c_str());
            m_listCtrl.SetItemText(row, 4, g->getID().c_str());
            m_listCtrl.SetItemText(row, 5, SERVO::CServoUtilsTool::getMaterialsTypeText(g->getType()).c_str());
            m_listCtrl.SetItemText(row, 6, SERVO::CServoUtilsTool::getGlassStateText(g->state()).c_str());
            m_listCtrl.SetItemText(row, 7, CToolUnits::TimePointToLocalString(g->tStart()).c_str());
            m_listCtrl.SetItemText(row, 8, CToolUnits::TimePointToLocalString(g->tEnd()).c_str());
            m_listCtrl.SetItemText(row, 9, g->getBuddyId().c_str());
            m_listCtrl.SetItemText(row, 10, SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)g->getAOIInspResult()).c_str());
            m_listCtrl.SetItemText(row, 11, g->getPathDescription().c_str());
            m_listCtrl.SetItemText(row, 12, g->getParamsDescription().c_str());
            rowsToRedraw.push_back(row);
            // â€”— é¡ºå¸¦åˆ·æ–° buddy å­è¡Œï¼ˆå¦‚果它已在可见表里)——
            if (SERVO::CGlass* b = g->getBuddy()) {
                CString buddyCidCs = b->getID().c_str();
#ifdef _UNICODE
                std::string bid = CT2A(buddyCidCs);
#else
                std::string bid = buddyCidCs.GetString();
#endif
                auto itChildAny = wipRowById.find(bid);
                if (itChildAny != wipRowById.end()) {
                    int crow = itChildAny->second.first;
                    // ç”Ÿæˆ buddy çš„列文本并一次性写回
                    auto bcols = makeColsFromWip(b);
                    ApplyColsToRow(m_listCtrl, crow, bcols);
                    rowsToRedraw.push_back(crow);
                }
                // å¦‚æžœ buddy è¡Œå½“前不存在,可保留你原来的“只在根下、且 buddy ä¸åœ¨å¯è§è¡¨æ—¶è¡¥å­é¡¹â€çš„逻辑
            }
            // â€”— åªå¯¹â€œæ ¹èŠ‚ç‚¹â€è¡¥å­é¡¹ï¼Œä¸”ä»…å½“ buddy å°šæœªå‡ºçŽ°åœ¨å¯è§è¡¨ï¼Œä¸”æ ¹ä¸‹ä¹Ÿæ²¡æœ‰è¯¥ buddy â€”—
            SERVO::CGlass* b = g->getBuddy();
            if (b) {
                // å½“前根容器?(子节点不作为容器)
                auto itRoot = wipRootById.find(cid);
                if (itRoot != wipRootById.end()) {
                    CExpandableListCtrl::Node* container = itRoot->second;
                    CString newBuddyCid = b->getID().c_str();
#ifdef _UNICODE
                    std::string newBid = CT2A(newBuddyCid);
#else
                    std::string newBid = newBuddyCid.GetString();
#endif
                    // çŽ°æœ‰å®¹å™¨ä¸‹çš„â€œç¬¬ä¸€ä¸ªå­ classId”(如果有的话)
                    CString oldChildCid;
                    if (!container->children.empty() && container->children[0] && container->children[0]->cols.size() > 4)
                        oldChildCid = container->children[0]->cols[4];
                    bool buddyExistsAnywhere = (wipRowById.find(newBid) != wipRowById.end());
                    bool hasChildAlready = NodeHasChildWithClassId(container, newBuddyCid);
                    // â€”— å…³é”®ï¼šå…³ç³»æ˜¯å¦å‘生变化?(oldChildCid ä¸Ž newBuddyCid ä¸åŒï¼‰
                    bool relationChanged =
                        (!oldChildCid.IsEmpty() && newBuddyCid.IsEmpty()) ||                             // ä¹‹å‰æœ‰å­ï¼ŒçŽ°åœ¨æ²¡ buddy
                        (oldChildCid.IsEmpty() && !newBuddyCid.IsEmpty()) ||                             // ä¹‹å‰æ²¡å­ï¼ŒçŽ°åœ¨æœ‰ buddy
                        (!oldChildCid.IsEmpty() && !newBuddyCid.IsEmpty() && oldChildCid.CompareNoCase(newBuddyCid) != 0); // æ”¹ buddy
                    if (relationChanged) {
                        // å…³ç³»å˜æ›´èµ°â€œç»“构重建”,避免重复或反向挂载
                        needRebuildAllForNewRoot = true;
                    }
                    else {
                        // å…³ç³»æœªå˜ï¼šè‹¥ buddy è¿˜ä¸åœ¨å¯è§è¡¨ä¸”容器下也没有,则补子
                        if (!buddyExistsAnywhere && !hasChildAlready) {
                            if (!needRebuildChildren) { CaptureUiState(m_listCtrl, savedSel, savedTop); }
                            needRebuildChildren = true;
                            auto cols = makeColsFromWip(b);
                            auto* ch = m_listCtrl.InsertChild(container, cols);
                            m_listCtrl.SetNodeColor(ch, kWipText, kWipChildBk);         // å­ï¼šæ›´æµ…
                            m_listCtrl.SetNodeColor(container, kWipText, kWipParentBk); // çˆ¶ï¼šåŸºç¡€ç»¿ï¼ˆå…œåº•纠正)
                        }
                        // è‹¥å·²æœ‰å­ï¼šé¡ºå¸¦æŠŠå­è¡Œæ–‡æœ¬åˆ·æ–°ä¸€ä¸‹ï¼ˆæ¯”如 AOI æ›´æ–°ï¼‰
                        else if (hasChildAlready) {
                            // æ‰¾åˆ°å¯¹åº”子并更新文本/cols,避免后续 Rebuild å€’回旧值
                            for (auto& ch : container->children) {
                                if (ch && ch->cols.size() > 4 && ch->cols[4].CompareNoCase(newBuddyCid) == 0) {
                                    auto cols = makeColsFromWip(b);
                                    ch->cols = cols; // åº•层数据
                                    // å¯è§è¡Œåˆ·æ–°
                                    for (int r = 0; r < m_listCtrl.GetItemCount(); ++r) {
                                        if (m_listCtrl.GetNodeByVisibleIndex(r) == ch.get()) {
                                            for (int c = 1; c < (int)cols.size(); ++c) {
                                                m_listCtrl.SetItemText(r, c, cols[c]);
                                                m_listCtrl.SetNodeColor(ch.get(), kWipText, kWipChildBk);   // ä¿è¯å­è¡Œæ˜¯æµ…色
                                                m_listCtrl.SetNodeColor(container, kWipText, kWipParentBk); // çˆ¶ä¿æŒåŸºç¡€ç»¿
                                            }
                                            rowsToRedraw.push_back(r);
                                            break;
                                        }
                                    }
                                    break;
                                }
                            }
                        }
                    }
                }
                // å½“前是“子节点”的情况:一律不挂子,交给重建(若父变更)
            }
            else {
                // æ²¡æœ‰ buddy:如果容器下现在有子,也算关系变化,触发重建
                auto itRoot = wipRootById.find(cid);
                if (itRoot != wipRootById.end()) {
                    CExpandableListCtrl::Node* container = itRoot->second;
                    if (!container->children.empty())
                        needRebuildAllForNewRoot = true;
                }
            }
        }
        else {
            // (B) ä¸å­˜åœ¨ï¼šæ–°å¢žæ ¹è¡Œâ€”—为保证“WIP æ°¸è¿œåœ¨é¡¶éƒ¨â€ï¼Œè§¦å‘全量重建
            needRebuildAllForNewRoot = true;
        }
    }
    // 4) åº”用 UI æ›´æ–°
    if (needRebuildAllForNewRoot) {
        // ç”¨ key(ClassID)保存并恢复,避免 Node* å¤±æ•ˆ
        auto selKeys = SnapshotSelectedKeys(m_listCtrl);
        auto topKey = SnapshotTopKey(m_listCtrl);
        UpdatePageData();                      // å…¨é‡é‡å»ºï¼ˆWIP é¡¶éƒ¨ï¼‰
        RestoreSelectionByKeys(m_listCtrl, selKeys);
        RestoreTopByKey(m_listCtrl, topKey);
    }
    else if (needRebuildChildren) {
        auto selKeys = SnapshotSelectedKeys(m_listCtrl);
        auto topKey = SnapshotTopKey(m_listCtrl);
        m_listCtrl.RebuildVisible();           // ä»…结构变化(加子)
        RestoreSelectionByKeys(m_listCtrl, selKeys);
        RestoreTopByKey(m_listCtrl, topKey);
    }
    else {
        for (int row : rowsToRedraw)           // ä»…文本变化
            m_listCtrl.RedrawItems(row, row);
    }
    // 5) é‡Šæ”¾ retain
    for (auto* g : tempRetain) g->release();
}
void CPageGlassList::InsertWipRow(SERVO::CGlass* /*pGlass*/)
{
    // ä¸å†ä½¿ç”¨
}
void CPageGlassList::UpdateWipRow(unsigned int /*index*/, SERVO::CGlass* /*pGlass*/)
{
    // ä¸å†ä½¿ç”¨
}
bool CPageGlassList::eraseGlassInVector(SERVO::CGlass* /*pGlass*/, std::vector<SERVO::CGlass*>& /*glasses*/)
{
    return false;
}
// ===== è¿‡æ»¤é€»è¾‘(原样保留) =====
// æ ¸å¿ƒï¼šWIP çš„ CGlass æ˜¯å¦å‘½ä¸­å½“前 Filters
// useEndTime=true æ—¶ç”¨ tEnd åˆ¤æ—¶é—´ï¼ˆæ¯”如“完成列表”用 t_end),默认按 tStart。
bool CPageGlassList::GlassMatchesFilters(const SERVO::CGlass& g,
    const GlassLogDb::Filters& f,
    bool useEndTime/* = false*/)
{
    // 1) ç²¾ç¡®å­—段
    if (f.classId && g.getID() != *f.classId)      return false;
    if (f.cassetteSeqNo && g.getCassetteSequenceNo() != *f.cassetteSeqNo) return false;
    if (f.jobSeqNo && g.getJobSequenceNo() != *f.jobSeqNo)     return false;
    // 2) å…³é”®å­—(与 DB ä¿æŒä¸€è‡´ï¼šclass_id / buddy_id / path / params / pretty)
    if (f.keyword) {
        const std::string& kw = *f.keyword;
        if (!(CToolUnits::containsCI(g.getID(), kw)
            || CToolUnits::containsCI(g.getBuddyId(), kw)
            || CToolUnits::containsCI(g.getPathDescription(), kw)
            || CToolUnits::containsCI(g.getParamsDescription(), kw)))
            return false;
    }
    // 3) æ—¶é—´ï¼ˆä¸Ž DB ä¿æŒä¸€è‡´ï¼šé»˜è®¤æŒ‰ t_start è¿‡æ»¤ï¼›éœ€è¦å¯åˆ‡åˆ° t_end)
    if (f.tStartFrom || f.tStartTo) {
        std::optional<std::chrono::system_clock::time_point> tp = useEndTime ? g.tEnd() : g.tStart();
        // çº¦å®šï¼šè‹¥æ²¡æœ‰å¯¹åº”时间戳,则视为不命中(与 DB ç›¸åŒï¼šNULL ä¸ä¼šå‘½ä¸­èŒƒå›´ï¼‰
        if (!tp) return false;
        if (f.tStartFrom && *tp < *f.tStartFrom) return false;
        if (f.tStartTo && *tp > *f.tStartTo)   return false;
    }
    return true;
}
SourceCode/Bond/Servo/CPageGlassList.h
@@ -1,5 +1,6 @@
#pragma once
#include "ListCtrlEx.h"
#include "CExpandableListCtrl.h"
#include "GlassLogDb.h"
// CPageGlassList å¯¹è¯æ¡†
@@ -18,8 +19,8 @@
    IObserver* m_pObserver;
    // æœç´¢å…³é”®å­—
    GlassLogDb::Filters m_filters;
    std::string m_strStatus;
    std::string m_strKeyword;
    // é¡µç 
    int m_nCurPage;
@@ -32,7 +33,11 @@
    // æŽ§ä»¶
    CDateTimeCtrl m_dateTimeStart;
    CDateTimeCtrl m_dateTimeEnd;
    CListCtrlEx m_listCtrl;
    CExpandableListCtrl m_listCtrl;
private:
    int  m_nColCount = 0;
    bool m_rebuilding = false;
private:
    void InitRxWindow();
@@ -40,14 +45,16 @@
    void InitStatusCombo();
    void InitTimeRangeCombo();
    void InitDateTimeControls();
    void LoadTransfers();
    void LoadData();
    void UpdatePageData();
    void UpdatePageControls();
    void UpdateDateFilter();
    /*
    void FillDataToListCtrl(const std::vector<TransferData>& vecData);
    void InsertTransferData(const TransferData& data);
    */
    void InsertWipRow(SERVO::CGlass* pGlass);
    static bool GlassMatchesFilters(const SERVO::CGlass& g,
        const GlassLogDb::Filters& f,
        bool useEndTime = false);
    void UpdateWipData();
    bool eraseGlassInVector(SERVO::CGlass* pGlass, std::vector<SERVO::CGlass*>& glasses);
    void UpdateWipRow(unsigned int index, SERVO::CGlass* pGlass);
// å¯¹è¯æ¡†æ•°æ®
#ifdef AFX_DESIGN_TIME
SourceCode/Bond/Servo/CPath.cpp
@@ -1,6 +1,7 @@
#include "stdafx.h"
#include "CPath.h"
#include "ToolUnits.h"
#include "CServoUtilsTool.h"
namespace SERVO {
@@ -15,10 +16,11 @@
        m_pNext = nullptr;
    }
    CPath::CPath(unsigned int nEqId, unsigned int nUnit)
    CPath::CPath(unsigned int nEqId, unsigned int nUnit, unsigned int nSlot)
    {
        m_nEqID = nEqId;
        m_nUnit = nUnit;
        m_nSlot = nSlot;
        m_timeOut = 0;
        m_timeIn = CToolUnits::getTimestamp();
        m_bProcessed = FALSE;
@@ -46,11 +48,17 @@
        strOut = strOut + ">";
    }
    void CPath::getSimpleDescription(std::string& strOut)
    {
        strOut = CServoUtilsTool::getEqUnitName(m_nEqID, m_nUnit, m_nSlot);
    }
    void CPath::serialize(CArchive& ar)
    {
        if (ar.IsStoring()) {
            ar << m_nEqID;
            ar << m_nUnit;
            ar << m_nSlot;
            ar << m_timeIn;
            ar << m_timeOut;
            ar << m_bProcessed;
@@ -65,6 +73,7 @@
            ar >> m_nEqID;
            ar >> m_nUnit;
            ar >> m_nSlot;
            ar >> m_timeIn;
            ar >> m_timeOut;
            ar >> m_bProcessed;
@@ -90,6 +99,11 @@
        return m_nUnit;
    }
    unsigned int CPath::getSlot()
    {
        return m_nSlot;
    }
    void CPath::setInTime(ULONGLONG time)
    {
        m_timeIn = time;
SourceCode/Bond/Servo/CPath.h
@@ -7,11 +7,12 @@
    {
    public:
        CPath();
        CPath(unsigned int nEqId, unsigned int nUnit);
        CPath(unsigned int nEqId, unsigned int nUnit, unsigned int nSlot);
        virtual ~CPath();
    public:
        void getDescription(std::string& strOut);
        void getSimpleDescription(std::string& strOut);
        void serialize(CArchive& ar);
        CPath* getPrev();
        CPath* getNext();
@@ -20,6 +21,7 @@
        CPath* getHeadPath();
        unsigned int getEqID();
        unsigned int getUnit();
        unsigned int getSlot();
        void setInTime(ULONGLONG time);
        ULONGLONG getInTime();
        void setOutTime(ULONGLONG time);
@@ -32,6 +34,7 @@
    private:    
        unsigned int m_nEqID;
        unsigned int m_nUnit;
        unsigned int m_nSlot;
        ULONGLONG m_timeIn;
        ULONGLONG m_timeOut;
        BOOL m_bProcessed;
SourceCode/Bond/Servo/CRobotTask.cpp
@@ -220,7 +220,7 @@
                    LOGI(_T("RobotTask已下发到EFEM"));
                }
                else {
                    LOGI(_T("RobotTask下发失败"));
                    LOGE(_T("RobotTask下发失败"));
                }
                return 0;
@@ -238,7 +238,7 @@
                    LOGI(_T("RobotTask/get已下发到EFEM"));
                }
                else {
                    LOGI(_T("RobotTask/get已下发失败"));
                    LOGE(_T("RobotTask/get已下发失败"));
                }
                return 0;
@@ -261,7 +261,7 @@
                    LOGI(_T("RobotTask/put已下发到EFEM"));
                }
                else {
                    LOGI(_T("RobotTask/put已下发失败"));
                    LOGE(_T("RobotTask/put已下发失败"));
                }
                return 0;
@@ -279,7 +279,7 @@
                    LOGI(_T("RobotTask/restore-put已下发到EFEM"));
                }
                else {
                    LOGI(_T("RobotTask/restore-put已下发失败"));
                    LOGE(_T("RobotTask/restore-put已下发失败"));
                }
                return 0;
SourceCode/Bond/Servo/CSVData.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
#include "stdafx.h"
#include "CSVData.h"
#include "ToolUnits.h"
namespace SERVO {
    CSVData::CSVData()
    {
    }
    CSVData::~CSVData()
    {
    }
    std::string& CSVData::getTime()
    {
        return m_strTime;
    }
    std::vector<uint8_t>& CSVData::getSVRawData()
    {
        return m_svRawData;
    }
    int CSVData::serialize(char* pszBuffer, int nBufferSize)
    {
        if (nBufferSize < 133) return -1;
        int index = 0;
        CToolUnits::convertString(&pszBuffer[index], 8, m_strTime);
        index += 8;
        memcpy(&pszBuffer[index], m_svRawData.data(), 125);
        index += 125;
        return 133;
    }
    int CSVData::unserialize(const char* pszBuffer, int nBufferSize)
    {
        if (nBufferSize < 133) return -1;
        int index = 0;
        CSVData svData;
        CToolUnits::convertString(&pszBuffer[index], 8 * 2, m_strTime);
        index += 8 * 2;
        m_svRawData.clear();
        m_svRawData.insert(m_svRawData.end(), (uint8_t*)(&pszBuffer[index]), (uint8_t*)(pszBuffer)+(125 * 2));
        index += 125 * 2;
        return 133;
    }
}
SourceCode/Bond/Servo/CSVData.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
#pragma once
namespace SERVO {
    class CSVData
    {
    public:
        CSVData();
        virtual ~CSVData();
    public:
        std::string& getTime();
        std::vector<uint8_t>& getSVRawData();
        int serialize(char* pszBuffer, int nBufferSize);
        int unserialize(const char* pszBuffer, int nBufferSize);
    private:
        std::string m_strTime;
        std::vector<uint8_t> m_svRawData;
    };
}
SourceCode/Bond/Servo/CServoUtilsTool.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,206 @@
#include "stdafx.h"
#include "CServoUtilsTool.h"
#include "Common.h"
namespace SERVO {
    CServoUtilsTool::CServoUtilsTool()
    {
    }
    CServoUtilsTool::~CServoUtilsTool()
    {
    }
    std::string CServoUtilsTool::getEqUnitName(int eqid, int unit)
    {
        char szBuffer[256];
        if (eqid == EQ_ID_LOADPORT1
            || eqid == EQ_ID_LOADPORT2
            || eqid == EQ_ID_LOADPORT3
            || eqid == EQ_ID_LOADPORT4
            ) {
            sprintf_s(szBuffer, 256, "Port%d(Slot%d)", eqid - EQ_ID_LOADPORT1 + 1, unit + 1);
            return std::string(szBuffer);
        }
        if (eqid == EQ_ID_ALIGNER) {
            return "Aligner";
        }
        if (eqid == EQ_ID_FLIPER) {
            return "Fliper";
        }
        if (eqid == EQ_ID_VACUUMBAKE) {
            if (unit == 0) return "烘烤A腔";
            if (unit == 1) return "烘烤B腔";
        }
        if (eqid == EQ_ID_VACUUMBAKE) {
            if (unit == 0) return "烘烤A腔";
            if (unit == 1) return "烘烤B腔";
        }
        if (eqid == EQ_ID_Bonder1) {
            return "Bonder1";
        }
        if (eqid == EQ_ID_Bonder2) {
            return "Bonder2";
        }
        if (eqid == EQ_ID_BAKE_COOLING) {
            if (unit == 0) return "后烘烤A腔";
            if (unit == 1) return "冷却A";
            if (unit == 0) return "后烘烤B腔";
            if (unit == 1) return "冷却B";
        }
        if (eqid == EQ_ID_MEASUREMENT) {
            return "AOI";
        }
        if (eqid == EQ_ID_ARM_TRAY1) {
            return "ARM1";
        }
        if (eqid == EQ_ID_ARM_TRAY2) {
            return "ARM2";
        }
        return "";
    }
    std::string CServoUtilsTool::getEqUnitName(int eqid, int unit, int slot)
    {
        char szBuffer[256];
        if (eqid == EQ_ID_LOADPORT1
            || eqid == EQ_ID_LOADPORT2
            || eqid == EQ_ID_LOADPORT3
            || eqid == EQ_ID_LOADPORT4
            ) {
            sprintf_s(szBuffer, 256, "Port%d(Slot%d)", eqid - EQ_ID_LOADPORT1 + 1, slot);
            return std::string(szBuffer);
        }
        if (eqid == EQ_ID_ALIGNER) {
            return "Aligner";
        }
        if (eqid == EQ_ID_FLIPER) {
            return "Fliper";
        }
        if (eqid == EQ_ID_VACUUMBAKE) {
            if (unit == 0) {
                sprintf_s(szBuffer, 256, "烘烤A腔(Slot%d)", slot);
                return std::string(szBuffer);
            }
            if (unit == 1) {
                sprintf_s(szBuffer, 256, "烘烤B腔(Slot%d)", slot);
                return std::string(szBuffer);
            }
        }
        if (eqid == EQ_ID_Bonder1) {
            sprintf_s(szBuffer, 256, "Bonder1(Slot%d)", slot);
            return std::string(szBuffer);
        }
        if (eqid == EQ_ID_Bonder2) {
            sprintf_s(szBuffer, 256, "Bonder2(Slot%d)", slot);
            return std::string(szBuffer);
        }
        if (eqid == EQ_ID_BAKE_COOLING) {
            if (slot == 0) return "后烘烤A腔";
            if (slot == 1) return "冷却A";
            if (slot == 0) return "后烘烤B腔";
            if (slot == 1) return "冷却B";
        }
        if (eqid == EQ_ID_MEASUREMENT) {
            return "AOI";
        }
        if (eqid == EQ_ID_ARM_TRAY1) {
            return "ARM1";
        }
        if (eqid == EQ_ID_ARM_TRAY2) {
            return "ARM2";
        }
        return "";
    }
    std::string CServoUtilsTool::getMaterialsTypeText(MaterialsType type)
    {
        if (type == MaterialsType::G1) {
            return "G1";
        }
        if (type == MaterialsType::G2) {
            return "G2";
        }
        return "";
    }
    std::string CServoUtilsTool::getGlassStateText(SERVO::GlsState state)
    {
        switch (state)
        {
        case SERVO::GlsState::NoState:
            return "NoState";
            break;
        case SERVO::GlsState::Queued:
            return "Queued";
            break;
        case SERVO::GlsState::InProcess:
            return "InProcess";
            break;
        case SERVO::GlsState::Paused:
            return "Paused";
            break;
        case SERVO::GlsState::Completed:
            return "Completed";
            break;
        case SERVO::GlsState::Aborted:
            return "Aborted";
            break;
        case SERVO::GlsState::Failed:
            return "Failed";
            break;
        default:
            return "";
            break;
        }
    }
    std::string CServoUtilsTool::getInspResultText(SERVO::InspResult result)
    {
        switch (result)
        {
        case SERVO::InspResult::NotInspected:
            return "";
            break;
        case SERVO::InspResult::Pass:
            return "Pass";
            break;
        case SERVO::InspResult::Fail:
            return "Fail";
            break;
        default:
            return "";
            break;
        }
    }
}
SourceCode/Bond/Servo/CServoUtilsTool.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
#pragma once
#include "ServoCommo.h"
#include "CGlass.h"
namespace SERVO {
    class CServoUtilsTool
    {
    public:
        CServoUtilsTool();
        virtual ~CServoUtilsTool();
    public:
        static std::string getEqUnitName(int eqid, int unit);
        static std::string getEqUnitName(int eqid, int unit, int slot);
        static std::string getMaterialsTypeText(MaterialsType type);
        static std::string getGlassStateText(SERVO::GlsState state);
        static std::string getInspResultText(SERVO::InspResult result);
    };
}
SourceCode/Bond/Servo/CVacuumBake.cpp
@@ -334,6 +334,26 @@
            }
        }
        // FAC Data Report
        addFacDataReportStep(0x16589, 0xc4d, 1);
        /*
        {
            CEqReadStep* pStep = new CEqReadStep(0x16589, 133 * 2,
                [&](void* pFrom, int code, const char* pszData, size_t size) -> int {
                    if (code == ROK && pszData != nullptr && size > 0) {
                        decodeFacDataReport((CStep*)pFrom, pszData, size);
                    }
                    return -1;
                });
            pStep->setName(STEP_EQ_FAC_DATA_REPORT);
            pStep->setProp("Port", (void*)1);
            pStep->setWriteSignalDev(0xc4d);
            if (addStep(STEP_ID_FAC_DATA_REPORT, pStep) != 0) {
                delete pStep;
            }
        }
        */
        // process start/end report
        {
            CEqReadStep* pStep = new CEqReadStep(0x15D3F, 13 * 2,
@@ -459,4 +479,128 @@
        return (int)params.size();
    }
    int CVacuumBake::parsingProcessData(const char* pszData, size_t size, std::vector<CParam>& params)
    {
        return parsingParams(pszData, size, params);
    }
    int CVacuumBake::parsingSVData(const char* pszData, size_t size, std::vector<CParam>& params)
    {
        /*
        1    A腔工艺运行步骤    1Word    123456
            2    A腔体真空规值    FLOAT    123.456
            3    A腔温控表1当前值    2Word    12345.6
            4    A腔温控表2当前值    2Word    12345.6
            5    A腔温控表4当前值    2Word    12345.6
            6    A腔温控表5当前值    2Word    12345.6
            7    A腔温控表6当前值    2Word    12345.6
            8    A腔温控表7当前值    2Word    12345.6
            9    A腔烘烤剩余时间    1Word    12345.6
            10    B腔工艺运行步骤    1Word    123456
            11    B腔体真空规值    FLOBT    123.456
            12    B腔温控表1当前值    2Word    12345.6
            13    B腔温控表2当前值    2Word    12345.6
            14    B腔温控表4当前值    2Word    12345.6
            15    B腔温控表5当前值    2Word    12345.6
            16    B腔温控表6当前值    2Word    12345.6
            17    B腔温控表7当前值    2Word    12345.6
            18    B腔烘烤剩余时间    1Word    12345.6
    */
        ASSERT(pszData);
        if (size < 125) return 0;
        int i = 0, v;
        // 1.A腔工艺运行步骤
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("A腔工艺运行步骤", "", this->getName().c_str(), v));
        i += 2;
        // 2.A腔体真空规值
        params.push_back(CParam("A腔体真空规值", "", this->getName().c_str(), (double)toFloat(&pszData[i])));
        i += 4;
        // 3.A腔温控表1当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("A腔温控表1当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 4.A腔温控表2当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("A腔温控表2当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 5.A腔温控表4当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("A腔温控表4当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 6.A腔温控表5当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("A腔温控表5当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 7.A腔温控表6当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("A腔温控表6当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 8.A腔温控表7当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("A腔温控表7当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 9.A腔烘烤剩余时间
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("A腔烘烤剩余时间", "", this->getName().c_str(), v * 0.1f));
        i += 2;
        // 10.B腔工艺运行步骤
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("B腔工艺运行步骤", "", this->getName().c_str(), v));
        i += 2;
        // 11.A腔体真空规值
        params.push_back(CParam("B腔体真空规值", "", this->getName().c_str(), (double)toFloat(&pszData[i])));
        i += 4;
        // 12.B腔温控表1当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("B腔温控表1当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 13.B腔温控表2当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("B腔温控表2当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 14.B腔温控表4当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("B腔温控表4当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 15.B腔温控表5当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("B腔温控表5当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 16.B腔温控表6当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("B腔温控表6当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 17.B腔温控表7当前值
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("B腔温控表7当前值", "", this->getName().c_str(), v * 0.1f));
        i += 4;
        // 18.B腔烘烤剩余时间
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("B腔烘烤剩余时间", "", this->getName().c_str(), v * 0.1f));
        i += 2;
        return (int)params.size();
    }
}
SourceCode/Bond/Servo/CVacuumBake.h
@@ -23,6 +23,8 @@
        virtual int recvIntent(CPin* pPin, CIntent* pIntent);
        virtual int getIndexerOperationModeBaseValue();
        virtual int parsingParams(const char* pszData, size_t size, std::vector<CParam>& parsms);
        virtual int parsingProcessData(const char* pszData, size_t size, std::vector<CParam>& parsms);
        virtual int parsingSVData(const char* pszData, size_t size, std::vector<CParam>& parsms);
    };
}
SourceCode/Bond/Servo/DevicePropertyDlg.cpp
@@ -41,7 +41,7 @@
    // TODO:  åœ¨æ­¤æ·»åŠ é¢å¤–çš„åˆå§‹åŒ–
    SERVO::CEquipment* pEquipment = theApp.m_model.m_master.getEquipment(m_nDeviceID);
    if (nullptr == pEquipment) {
        LOGI("<设备ID:%d>获取设备属性失败。", m_nDeviceID);
        LOGE("<设备ID:%d>获取设备属性失败。", m_nDeviceID);
        return FALSE;
    }
SourceCode/Bond/Servo/GlassJson.cpp
@@ -178,6 +178,7 @@
                Json::Value n(Json::objectValue);
                n["eq_id"] = p->getEqID();
                n["unit"] = p->getUnit();
                n["slot"] = p->getUnit();
                put_ull_as_str(n, "time_in", static_cast<unsigned long long>(p->getInTime()));
                put_ull_as_str(n, "time_out", static_cast<unsigned long long>(p->getOutTime()));
                n["processed"] = p->isProcessEnd() ? true : false;
@@ -199,7 +200,7 @@
    // åŸºæœ¬
    g.setID(JStr(root, "id").c_str());
    g.setType(static_cast<MaterialsType>(JInt(root, "materials", 0)));
    g.getBuddyId() = JStr(root, "buddy_id");
    g.setBuddyId(JStr(root, "buddy_id"));
    g.setScheduledForProcessing(JBool(root, "scheduled") ? TRUE : FALSE);
    g.m_failReason = JStr(root, "fail_reason");
    g.setOriginPort(JInt(root, "origin_port", 0), JInt(root, "origin_slot", 0));
@@ -293,7 +294,8 @@
        for (const auto& n : root["path"]) {
            unsigned eq = JUInt(n, "eq_id", 0);
            unsigned unit = JUInt(n, "unit", 0);
            g.addPath(eq, unit);
            unsigned slot = JUInt(n, "slot", 0);
            g.addPath(eq, unit, slot);
            CPath* tail = nullptr;
            if (auto head = g.getPath()) tail = head->getTailPath();
SourceCode/Bond/Servo/GlassLogDb.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,526 @@
// GlassLogDb.cpp - å•例封装:SQLite å†™å…¥/查询/分页/统计/CSV å¯¼å‡ºï¼ˆå·²å¯¹æŽ¥ SERVO::CGlass)
#include "stdafx.h"
#include "GlassLogDb.h"
#include "sqlite3.h"
#include <stdexcept>
#include <sstream>
#include <iomanip>
#include <ctime>
#include <fstream>
#include "GlassJson.h"
using namespace SERVO;
#ifndef GLASS_LOG_TABLE
#define GLASS_LOG_TABLE "glass_log"
#endif
// ================== å·¥å…·å‡½æ•° ==================
static void throwIf(int rc, sqlite3* db, const char* msg) {
    if (rc != SQLITE_OK && rc != SQLITE_DONE && rc != SQLITE_ROW) {
        std::ostringstream oss;
        oss << msg << " (rc=" << rc << "): " << (db ? sqlite3_errmsg(db) : "null db");
        throw std::runtime_error(oss.str());
    }
}
static inline const char* safe_text(sqlite3_stmt* s, int col) {
    const unsigned char* p = sqlite3_column_text(s, col);
    return p ? reinterpret_cast<const char*>(p) : "";
}
static std::string csvEscape(const std::string& s) {
    bool needQuote = s.find_first_of(",\"\n\r") != std::string::npos;
    if (!needQuote) return s;
    std::string out; out.reserve(s.size() + 2);
    out.push_back('"');
    for (char c : s) out += (c == '"') ? "\"\"" : std::string(1, c);
    out.push_back('"');
    return out;
}
static std::string toIso8601String(const std::optional<std::chrono::system_clock::time_point>& tp) {
    if (!tp.has_value()) return "";
    using namespace std::chrono;
    auto t = system_clock::to_time_t(*tp);
    std::tm tm{};
#if defined(_WIN32)
    gmtime_s(&tm, &t);
#else
    gmtime_r(&t, &tm);
#endif
    std::ostringstream oss;
    oss << std::put_time(&tm, "%Y-%m-%dT%H:%M:%SZ");
    return oss.str();
}
// ================== å•例静态成员 ==================
std::unique_ptr<GlassLogDb> GlassLogDb::s_inst;
std::mutex GlassLogDb::s_instMtx;
// ================== å•例接口实现 ==================
void GlassLogDb::Init(const std::string& dbPath) {
    std::lock_guard<std::mutex> g(s_instMtx);
    if (!s_inst) {
        s_inst.reset(new GlassLogDb(dbPath));
    }
    else if (s_inst->dbPath_ != dbPath) {
        s_inst->reopenInternal(dbPath);
    }
}
GlassLogDb& GlassLogDb::Instance() {
    std::lock_guard<std::mutex> g(s_instMtx);
    if (!s_inst) throw std::runtime_error("GlassLogDb::Instance() called before Init()");
    return *s_inst;
}
bool GlassLogDb::IsInitialized() noexcept {
    std::lock_guard<std::mutex> g(s_instMtx);
    return static_cast<bool>(s_inst);
}
void GlassLogDb::Reopen(const std::string& dbPath) {
    std::lock_guard<std::mutex> g(s_instMtx);
    if (!s_inst) {
        s_inst.reset(new GlassLogDb(dbPath));
    }
    else {
        s_inst->reopenInternal(dbPath);
    }
}
std::string GlassLogDb::CurrentPath() {
    std::lock_guard<std::mutex> g(s_instMtx);
    return s_inst ? s_inst->dbPath_ : std::string();
}
// ================== æž„造/打开/关闭/重开 ==================
GlassLogDb::GlassLogDb(const std::string& dbPath) {
    openDb(dbPath);
}
void GlassLogDb::openDb(const std::string& dbPath) {
    int rc = sqlite3_open_v2(dbPath.c_str(), &db_,
        SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX,
        nullptr);
    if (rc != SQLITE_OK) {
        std::string err = db_ ? sqlite3_errmsg(db_) : "open failed";
        if (db_) sqlite3_close(db_);
        db_ = nullptr;
        throw std::runtime_error("Failed to open sqlite DB: " + err + " | path=" + dbPath);
    }
    dbPath_ = dbPath;
    ensureSchema();
    prepareStatements();
}
void GlassLogDb::closeDb() noexcept {
    finalizeStatements();
    if (db_) {
        sqlite3_close(db_);
        db_ = nullptr;
    }
}
void GlassLogDb::reopenInternal(const std::string& dbPath) {
    std::lock_guard<std::mutex> lk(mtx_);
    if (dbPath == dbPath_) return;
    closeDb();
    openDb(dbPath);
}
// ================== æžæž„ ==================
GlassLogDb::~GlassLogDb() {
    closeDb();
}
// ================== DDL & é¢„编译 ==================
void GlassLogDb::ensureSchema() {
    const char* ddl =
        "CREATE TABLE IF NOT EXISTS " GLASS_LOG_TABLE " ("
        "  id INTEGER PRIMARY KEY AUTOINCREMENT,"
        "  cassette_seq_no INTEGER,"
        "  job_seq_no INTEGER,"
        "  class_id TEXT,"
        "  material_type INTEGER,"
        "  state INTEGER,"
        "  t_start TEXT,"      // ISO8601 UTC(可为 NULL)
        "  t_end TEXT,"        // ISO8601 UTC(可为 NULL)
        "  buddy_id TEXT,"
        "  aoi_result INTEGER,"
        "  path TEXT,"
        "  params TEXT,"
        "  pretty TEXT"
        ");"
        "CREATE INDEX IF NOT EXISTS idx_glass_key "
        "  ON " GLASS_LOG_TABLE " (cassette_seq_no, job_seq_no);"
        "CREATE INDEX IF NOT EXISTS idx_class_id "
        "  ON " GLASS_LOG_TABLE " (class_id);"
        "CREATE INDEX IF NOT EXISTS idx_t_start "
        "  ON " GLASS_LOG_TABLE " (t_start);";
    char* errMsg = nullptr;
    int rc = sqlite3_exec(db_, ddl, nullptr, nullptr, &errMsg);
    if (rc != SQLITE_OK) {
        std::string err = errMsg ? errMsg : "unknown";
        sqlite3_free(errMsg);
        throw std::runtime_error("Failed to create schema: " + err);
    }
}
void GlassLogDb::prepareStatements() {
    const char* sql =
        "INSERT INTO " GLASS_LOG_TABLE " ("
        " cassette_seq_no, job_seq_no, class_id, material_type, state,"
        " t_start, t_end, buddy_id, aoi_result, path, params, pretty)"
        " VALUES (?,?,?,?,?,?,?,?,?,?,?,?);";
    int rc = sqlite3_prepare_v2(db_, sql, -1, &stmtInsert_, nullptr);
    throwIf(rc, db_, "prepare insert");
}
void GlassLogDb::finalizeStatements() noexcept {
    if (stmtInsert_) {
        sqlite3_finalize(stmtInsert_);
        stmtInsert_ = nullptr;
    }
}
// ================== äº‹åŠ¡ ==================
void GlassLogDb::beginTransaction() {
    std::lock_guard<std::mutex> lk(mtx_);
    char* err = nullptr;
    int rc = sqlite3_exec(db_, "BEGIN IMMEDIATE;", nullptr, nullptr, &err);
    if (rc != SQLITE_OK) {
        std::string e = err ? err : "unknown";
        sqlite3_free(err);
        throw std::runtime_error("BEGIN failed: " + e);
    }
}
void GlassLogDb::commit() {
    std::lock_guard<std::mutex> lk(mtx_);
    char* err = nullptr;
    int rc = sqlite3_exec(db_, "COMMIT;", nullptr, nullptr, &err);
    if (rc != SQLITE_OK) {
        std::string e = err ? err : "unknown";
        sqlite3_free(err);
        throw std::runtime_error("COMMIT failed: " + e);
    }
}
void GlassLogDb::rollback() {
    std::lock_guard<std::mutex> lk(mtx_);
    char* err = nullptr;
    sqlite3_exec(db_, "ROLLBACK;", nullptr, nullptr, &err);
    if (err) sqlite3_free(err);
}
// ================== æ—¶é—´æ ¼å¼ ==================
std::string GlassLogDb::toIso8601Utc(std::chrono::system_clock::time_point tp) {
    using namespace std::chrono;
    auto t = system_clock::to_time_t(tp);
    std::tm tm{};
#if defined(_WIN32)
    gmtime_s(&tm, &t);
#else
    gmtime_r(&t, &tm);
#endif
    std::ostringstream oss;
    oss << std::put_time(&tm, "%Y-%m-%dT%H:%M:%SZ");
    return oss.str();
}
// ================== æ’入实现 ==================
long long GlassLogDb::doInsert(
    int cassetteSeqNo,
    int jobSeqNo,
    const std::string& classId,
    int materialType,
    int state,
    std::optional<std::chrono::system_clock::time_point> tStart,
    std::optional<std::chrono::system_clock::time_point> tEnd,
    const std::string& buddyId,
    int aoiResult,
    const std::string& pathDesc,
    const std::string& paramsDesc,
    const std::string& prettyString)
{
    std::lock_guard<std::mutex> lk(mtx_);
    int idx = 1;
    throwIf(sqlite3_reset(stmtInsert_), db_, "reset insert");
    throwIf(sqlite3_clear_bindings(stmtInsert_), db_, "clear bindings");
    throwIf(sqlite3_bind_int(stmtInsert_, idx++, cassetteSeqNo), db_, "bind cassette");
    throwIf(sqlite3_bind_int(stmtInsert_, idx++, jobSeqNo), db_, "bind job");
    throwIf(sqlite3_bind_text(stmtInsert_, idx++, classId.c_str(), -1, SQLITE_TRANSIENT), db_, "bind class_id");
    throwIf(sqlite3_bind_int(stmtInsert_, idx++, materialType), db_, "bind material_type");
    throwIf(sqlite3_bind_int(stmtInsert_, idx++, state), db_, "bind state");
    if (tStart.has_value()) {
        auto s = toIso8601Utc(*tStart);
        throwIf(sqlite3_bind_text(stmtInsert_, idx++, s.c_str(), -1, SQLITE_TRANSIENT), db_, "bind t_start");
    }
    else {
        throwIf(sqlite3_bind_null(stmtInsert_, idx++), db_, "bind t_start null");
    }
    if (tEnd.has_value()) {
        auto e = toIso8601Utc(*tEnd);
        throwIf(sqlite3_bind_text(stmtInsert_, idx++, e.c_str(), -1, SQLITE_TRANSIENT), db_, "bind t_end");
    }
    else {
        throwIf(sqlite3_bind_null(stmtInsert_, idx++), db_, "bind t_end null");
    }
    throwIf(sqlite3_bind_text(stmtInsert_, idx++, buddyId.c_str(), -1, SQLITE_TRANSIENT), db_, "bind buddy_id");
    throwIf(sqlite3_bind_int(stmtInsert_, idx++, aoiResult), db_, "bind aoi_result");
    throwIf(sqlite3_bind_text(stmtInsert_, idx++, pathDesc.c_str(), -1, SQLITE_TRANSIENT), db_, "bind path");
    throwIf(sqlite3_bind_text(stmtInsert_, idx++, paramsDesc.c_str(), -1, SQLITE_TRANSIENT), db_, "bind params");
    throwIf(sqlite3_bind_text(stmtInsert_, idx++, prettyString.c_str(), -1, SQLITE_TRANSIENT), db_, "bind pretty");
    int rc = sqlite3_step(stmtInsert_);
    throwIf(rc, db_, "insert step");
    return sqlite3_last_insert_rowid(db_);
}
long long GlassLogDb::insertFromCGlass(CGlass& g)
{
    const int cassette = static_cast<int>(g.getCassetteSequenceNo());
    const int job = static_cast<int>(g.getJobSequenceNo());
    const std::string& classId = g.getID();
    const int material = static_cast<int>(g.getType());
    const int state = static_cast<int>(g.state());
    const std::string& buddy = g.getBuddyId();
    const int aoiResult = static_cast<int>(g.getAOIInspResult());
    const std::string pathDesc = g.getPathDescription();
    const std::string paramsDesc = g.getParamsDescription();
    const std::string pretty = GlassJson::ToPrettyString(g);
    auto tStart = g.tStart();
    auto tEnd = g.tEnd();
    return doInsert(cassette, job, classId, material, state,
        tStart, tEnd, buddy, aoiResult, pathDesc, paramsDesc, pretty);
}
long long GlassLogDb::insertExplicit(
    int cassetteSeqNo,
    int jobSeqNo,
    const std::string& classId,
    int materialType,
    int state,
    std::optional<std::chrono::system_clock::time_point> tStart,
    std::optional<std::chrono::system_clock::time_point> tEnd,
    const std::string& buddyId,
    int aoiResult,
    const std::string& pathDesc,
    const std::string& paramsDesc,
    const std::string& prettyString)
{
    return doInsert(cassetteSeqNo, jobSeqNo, classId, materialType, state,
        tStart, tEnd, buddyId, aoiResult, pathDesc, paramsDesc, prettyString);
}
// ================== è¿‡æ»¤æž„造(where + bind) ==================
static std::string buildWhereSql(const GlassLogDb::Filters& f) {
    std::ostringstream where;
    bool first = true;
    auto add = [&](const std::string& cond) {
        if (first) { where << " WHERE " << cond; first = false; }
        else { where << " AND " << cond; }
    };
    if (f.classId)       add("class_id = ?");
    if (f.cassetteSeqNo) add("cassette_seq_no = ?");
    if (f.jobSeqNo)      add("job_seq_no = ?");
    if (f.keyword)       add("(class_id LIKE ? OR buddy_id LIKE ? OR path LIKE ? OR params LIKE ? OR pretty LIKE ?)");
    if (f.tStartFrom && f.tStartTo) add("(t_start >= ? AND t_start <= ?)");
    else if (f.tStartFrom)          add("t_start >= ?");
    else if (f.tStartTo)            add("t_start <= ?");
    return where.str();
}
static void bindFilters(sqlite3_stmt* stmt, sqlite3* db, int& idx, const GlassLogDb::Filters& f) {
    if (f.classId)       throwIf(sqlite3_bind_text(stmt, idx++, f.classId->c_str(), -1, SQLITE_TRANSIENT), db, "bind classId");
    if (f.cassetteSeqNo) throwIf(sqlite3_bind_int(stmt, idx++, *f.cassetteSeqNo), db, "bind cassette");
    if (f.jobSeqNo)      throwIf(sqlite3_bind_int(stmt, idx++, *f.jobSeqNo), db, "bind job");
    if (f.keyword) {
        std::string kw = "%" + *f.keyword + "%";
        for (int i = 0; i < 5; ++i)
            throwIf(sqlite3_bind_text(stmt, idx++, kw.c_str(), -1, SQLITE_TRANSIENT), db, "bind keyword");
    }
    if (f.tStartFrom && f.tStartTo) {
        std::string s = toIso8601String(f.tStartFrom);
        std::string e = toIso8601String(f.tStartTo);
        throwIf(sqlite3_bind_text(stmt, idx++, s.c_str(), -1, SQLITE_TRANSIENT), db, "bind tStartFrom");
        throwIf(sqlite3_bind_text(stmt, idx++, e.c_str(), -1, SQLITE_TRANSIENT), db, "bind tStartTo");
    }
    else if (f.tStartFrom) {
        std::string s = toIso8601String(f.tStartFrom);
        throwIf(sqlite3_bind_text(stmt, idx++, s.c_str(), -1, SQLITE_TRANSIENT), db, "bind tStartFrom");
    }
    else if (f.tStartTo) {
        std::string e = toIso8601String(f.tStartTo);
        throwIf(sqlite3_bind_text(stmt, idx++, e.c_str(), -1, SQLITE_TRANSIENT), db, "bind tStartTo");
    }
}
// ================== æŸ¥è¯¢ / ç»Ÿè®¡ / åˆ†é¡µ ==================
std::vector<GlassLogDb::Row> GlassLogDb::query(
    const Filters& filters,
    int limit,
    int offset)
{
    std::lock_guard<std::mutex> lk(mtx_);
    std::ostringstream sql;
    sql << "SELECT id, cassette_seq_no, job_seq_no, class_id, material_type, state,"
        " IFNULL(strftime('%Y-%m-%d %H:%M:%S', t_start, 'localtime'), ''),"
        " IFNULL(strftime('%Y-%m-%d %H:%M:%S', t_end,   'localtime'), ''),"
        " buddy_id, aoi_result, path, params, pretty"
        " FROM " GLASS_LOG_TABLE;
    std::string where = buildWhereSql(filters);
    sql << where
        << " ORDER BY id DESC"
        << " LIMIT " << (limit < 0 ? 0 : limit)
        << " OFFSET " << (offset < 0 ? 0 : offset);
    sqlite3_stmt* stmt = nullptr;
    throwIf(sqlite3_prepare_v2(db_, sql.str().c_str(), -1, &stmt, nullptr), db_, "prepare query");
    int idx = 1;
    bindFilters(stmt, db_, idx, filters);
    std::vector<Row> rows;
    for (;;) {
        int rc = sqlite3_step(stmt);
        if (rc == SQLITE_ROW) {
            Row r;
            r.id = sqlite3_column_int64(stmt, 0);
            r.cassetteSeqNo = sqlite3_column_int(stmt, 1);
            r.jobSeqNo = sqlite3_column_int(stmt, 2);
            r.classId = safe_text(stmt, 3);
            r.materialType = sqlite3_column_int(stmt, 4);
            r.state = sqlite3_column_int(stmt, 5);
            r.tStart = safe_text(stmt, 6);
            r.tEnd = safe_text(stmt, 7);
            r.buddyId = safe_text(stmt, 8);
            r.aoiResult = sqlite3_column_int(stmt, 9);
            r.path = safe_text(stmt, 10);
            r.params = safe_text(stmt, 11);
            r.pretty = safe_text(stmt, 12);
            rows.push_back(std::move(r));
        }
        else if (rc == SQLITE_DONE) {
            break;
        }
        else {
            sqlite3_finalize(stmt);
            throwIf(rc, db_, "query step");
        }
    }
    sqlite3_finalize(stmt);
    return rows;
}
long long GlassLogDb::count(const Filters& filters) {
    std::lock_guard<std::mutex> lk(mtx_);
    std::ostringstream sql;
    sql << "SELECT COUNT(1) FROM " GLASS_LOG_TABLE;
    std::string where = buildWhereSql(filters);
    sql << where;
    sqlite3_stmt* stmt = nullptr;
    throwIf(sqlite3_prepare_v2(db_, sql.str().c_str(), -1, &stmt, nullptr), db_, "prepare count");
    int idx = 1;
    bindFilters(stmt, db_, idx, filters);
    long long total = 0;
    int rc = sqlite3_step(stmt);
    if (rc == SQLITE_ROW) {
        total = sqlite3_column_int64(stmt, 0);
    }
    else if (rc != SQLITE_DONE) {
        sqlite3_finalize(stmt);
        throwIf(rc, db_, "count step");
    }
    sqlite3_finalize(stmt);
    return total;
}
GlassLogDb::Page GlassLogDb::queryPaged(
    const Filters& filters,
    int limit,
    int offset)
{
    Page p;
    p.limit = (limit < 0 ? 0 : limit);
    p.offset = (offset < 0 ? 0 : offset);
    p.total = count(filters);
    p.items = query(filters, p.limit, p.offset);
    return p;
}
// ================== å¯¼å‡º CSV(全部符合条件) ==================
long long GlassLogDb::exportCsv(const std::string& csvPath, const Filters& filters) {
    std::lock_guard<std::mutex> lk(mtx_);
    std::ofstream ofs(csvPath, std::ios::binary);
    if (!ofs) {
        throw std::runtime_error("Failed to open csv for write: " + csvPath);
    }
    // è¡¨å¤´
    ofs << "id,cassette_seq_no,job_seq_no,class_id,material_type,state,t_start,t_end,"
        "buddy_id,aoi_result,path,params,pretty\n";
    std::ostringstream sql;
    sql << "SELECT id, cassette_seq_no, job_seq_no, class_id, material_type, state,"
        " IFNULL(strftime('%Y-%m-%d %H:%M:%S', t_start, 'localtime'), ''),"
        " IFNULL(strftime('%Y-%m-%d %H:%M:%S', t_end,   'localtime'), ''),"
        " buddy_id, aoi_result, path, params, pretty"
        " FROM " GLASS_LOG_TABLE;
    std::string where = buildWhereSql(filters);
    sql << where << " ORDER BY id DESC";
    sqlite3_stmt* stmt = nullptr;
    throwIf(sqlite3_prepare_v2(db_, sql.str().c_str(), -1, &stmt, nullptr), db_, "prepare export");
    int idx = 1;
    bindFilters(stmt, db_, idx, filters);
    long long rows = 0;
    for (;;) {
        int rc = sqlite3_step(stmt);
        if (rc == SQLITE_ROW) {
            std::string id = std::to_string(sqlite3_column_int64(stmt, 0));
            std::string cassette = std::to_string(sqlite3_column_int(stmt, 1));
            std::string job = std::to_string(sqlite3_column_int(stmt, 2));
            std::string class_id = safe_text(stmt, 3);
            std::string material = std::to_string(sqlite3_column_int(stmt, 4));
            std::string state = std::to_string(sqlite3_column_int(stmt, 5));
            std::string t_start = safe_text(stmt, 6);
            std::string t_end = safe_text(stmt, 7);
            std::string buddy = safe_text(stmt, 8);
            std::string aoi = std::to_string(sqlite3_column_int(stmt, 9));
            std::string path = safe_text(stmt, 10);
            std::string params = safe_text(stmt, 11);
            std::string pretty = safe_text(stmt, 12);
            ofs << id << ','
                << cassette << ','
                << job << ','
                << csvEscape(class_id) << ','
                << material << ','
                << state << ','
                << t_start << ','
                << t_end << ','
                << csvEscape(buddy) << ','
                << aoi << ','
                << csvEscape(path) << ','
                << csvEscape(params) << ','
                << csvEscape(pretty) << '\n';
            ++rows;
        }
        else if (rc == SQLITE_DONE) {
            break;
        }
        else {
            sqlite3_finalize(stmt);
            throwIf(rc, db_, "export step");
        }
    }
    sqlite3_finalize(stmt);
    ofs.flush();
    return rows;
}
SourceCode/Bond/Servo/GlassLogDb.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,170 @@
#pragma once
// GlassLogDb.h - å•例封装:SQLite å†™å…¥/查询/分页/统计/CSV å¯¼å‡ºï¼ˆå·²å¯¹æŽ¥ SERVO::CGlass)
#include <string>
#include <vector>
#include <mutex>
#include <optional>
#include <chrono>
#include <cstdint>
#include <memory>   // std::unique_ptr
#include "CGlass.h"   // éœ€è¦ SERVO::CGlass
// å¯åœ¨ç¼–译命令或本头文件前自定义表名:#define GLASS_LOG_TABLE "your_table_name"
#ifndef GLASS_LOG_TABLE
#define GLASS_LOG_TABLE "glass_log"
#endif
// å‰ç½®å£°æ˜Žï¼Œé¿å…å¤´æ–‡ä»¶ä¾èµ– sqlite3.h
struct sqlite3;
struct sqlite3_stmt;
class GlassLogDb {
public:
    // ====== å•例接口 ======
    // åˆå§‹åŒ–(或切换)数据库文件路径;第一次调用会创建实例
    static void Init(const std::string& dbPath);
    // èŽ·å–å…¨å±€å®žä¾‹ï¼›è‹¥æœªåˆå§‹åŒ–ä¼šæŠ›å¼‚å¸¸
    static GlassLogDb& Instance();
    // æ˜¯å¦å·²åˆå§‹åŒ–
    static bool IsInitialized() noexcept;
    // è¿è¡Œä¸­åˆ‡æ¢åˆ°å¦ä¸€ä»½æ•°æ®åº“文件(等同 Init,不存在则创建)
    static void Reopen(const std::string& dbPath);
    // å½“前数据库文件路径(未初始化返回空串)
    static std::string CurrentPath();
    // ====== äº‹åŠ¡æŽ§åˆ¶ ======
    void beginTransaction();
    void commit();
    void rollback();
    // ====== å†™å…¥ ======
    // ç›´æŽ¥ä»Ž SERVO::CGlass å†™å…¥ï¼ˆæ³¨æ„ï¼šå‚数为 éž const å¼•用,因为你的若干 getter éž const)
    long long insertFromCGlass(SERVO::CGlass& g);
    // æ˜¾å¼å‚数插入(tStart/tEnd å¯ç©ºï¼›å‡å†™å…¥ UTC ISO8601 æ–‡æœ¬æˆ– NULL)
    long long insertExplicit(
        int cassetteSeqNo,
        int jobSeqNo,
        const std::string& classId,
        int materialType,
        int state,
        std::optional<std::chrono::system_clock::time_point> tStart,
        std::optional<std::chrono::system_clock::time_point> tEnd,
        const std::string& buddyId,
        int aoiResult,
        const std::string& pathDesc,
        const std::string& paramsDesc,
        const std::string& prettyString);
    // ====== æŸ¥è¯¢è¿”回结构 ======
    struct Row {
        long long id = 0;
        int cassetteSeqNo = 0;
        int jobSeqNo = 0;
        std::string classId;
        int materialType = 0;
        int state = 0;
        std::string tStart;   // ISO8601(库中为 NULL åˆ™ä¸ºç©ºä¸²ï¼‰
        std::string tEnd;     // ISO8601(库中为 NULL åˆ™ä¸ºç©ºä¸²ï¼‰
        std::string buddyId;
        int aoiResult = 0;
        std::string path;
        std::string params;
        std::string pretty;
    };
    // ====== è¿‡æ»¤æ¡ä»¶ ======
    struct Filters {
        std::optional<std::string> classId;       // ç²¾ç¡®åŒ¹é… class_id
        std::optional<int> cassetteSeqNo;
        std::optional<int> jobSeqNo;
        // å…³é”®å­—模糊:对 class_id / buddy_id / path / params / pretty åš OR LIKE
        std::optional<std::string> keyword;
        // æ—¶é—´èŒƒå›´ï¼šå¯¹ t_start è¿‡æ»¤ï¼Œå«è¾¹ç•Œï¼ˆISO8601 æ–‡æœ¬æ¯”较)
        std::optional<std::chrono::system_clock::time_point> tStartFrom;
        std::optional<std::chrono::system_clock::time_point> tStartTo;
    };
    // ====== æŸ¥è¯¢ / ç»Ÿè®¡ / åˆ†é¡µ ======
    // åˆ†é¡µæŸ¥è¯¢ï¼ˆlimit/offset),按 id DESC
    std::vector<Row> query(
        const Filters& filters = {},
        int limit = 50,
        int offset = 0);
    // ç»Ÿè®¡ä¸Ž filters ç›¸åŒæ¡ä»¶çš„æ€»æ•°
    long long count(const Filters& filters = {});
    struct Page {
        std::vector<Row> items;  // å½“前页数据
        long long total = 0;     // ç¬¦åˆæ¡ä»¶çš„æ€»è®°å½•æ•°
        int limit = 0;           // æœ¬æ¬¡æŸ¥è¯¢çš„页容量
        int offset = 0;          // æœ¬æ¬¡æŸ¥è¯¢çš„起始偏移
    };
    // ä¸€æ¬¡å–回列表 + æ€»æ•°
    Page queryPaged(
        const Filters& filters = {},
        int limit = 50,
        int offset = 0);
    // ====== å¯¼å‡º ======
    // å¯¼å‡ºæ»¡è¶³ filters çš„“全部记录”(不受分页限制)为 CSV(UTF-8)
    // è¿”回写出的数据行数(不含表头)
    long long exportCsv(const std::string& csvPath, const Filters& filters = {});
    // ====== æžæž„ / æ‹·è´æŽ§åˆ¶ ======
    ~GlassLogDb();
    GlassLogDb(const GlassLogDb&) = delete;
    GlassLogDb& operator=(const GlassLogDb&) = delete;
private:
    // ä»…允许通过单例接口构造
    explicit GlassLogDb(const std::string& dbPath);
    // å†…部打开/关闭/重开
    void openDb(const std::string& dbPath);
    void closeDb() noexcept;
    void reopenInternal(const std::string& dbPath);
    // å»ºè¡¨ / é¢„编译 / é‡Šæ”¾
    void ensureSchema();
    void prepareStatements();
    void finalizeStatements() noexcept;
    // å·¥å…·ï¼štime_point -> "YYYY-MM-DDTHH:MM:SSZ"(UTC)
    static std::string toIso8601Utc(std::chrono::system_clock::time_point tp);
    // å®žé™…插入执行(支持可空时间)
    long long doInsert(
        int cassetteSeqNo,
        int jobSeqNo,
        const std::string& classId,
        int materialType,
        int state,
        std::optional<std::chrono::system_clock::time_point> tStart,
        std::optional<std::chrono::system_clock::time_point> tEnd,
        const std::string& buddyId,
        int aoiResult,
        const std::string& pathDesc,
        const std::string& paramsDesc,
        const std::string& prettyString);
private:
    // SQLite å¥æŸ„
    sqlite3* db_ = nullptr;
    sqlite3_stmt* stmtInsert_ = nullptr;
    // å®žä¾‹å†…并发保护(同一连接上的写/读串行化)
    std::mutex mtx_;
    // å½“前数据库文件路径
    std::string dbPath_;
    // å•例静态对象与其互斥
    static std::unique_ptr<GlassLogDb> s_inst;
    static std::mutex s_instMtx;
};
SourceCode/Bond/Servo/ListCtrlEx.cpp
@@ -34,11 +34,12 @@
    {
        // æ ¹æ®åœ¨ SetItemColor(DWORD iItem, COLORREF color) è®¾ç½®çš„   
        // ITEM号和COLORREF åœ¨æ‘¸æ¿ä¸­æŸ¥æ‰¾ï¼Œç„¶åŽè¿›è¡Œé¢œè‰²èµ‹å€¼ã€‚
        //LISTITEMEX_9& itemex = m_listItemColor.GetAt(m_listItemColor.FindIndex(nmcd.dwItemSpec));
        //lplvdr->clrText = itemex.colText;
        //lplvdr->clrTextBk = itemex.colTextBk;
        //*pResult = CDRF_DODEFAULT;
        LISTITEMEX_9& itemex = m_listItemColor.GetAt(m_listItemColor.FindIndex(nmcd.dwItemSpec));
        lplvdr->clrText = itemex.colText;
        lplvdr->clrTextBk = itemex.colTextBk;
        *pResult = CDRF_DODEFAULT;
        /*
        if (nmcd.dwItemSpec % 2 == 0) {
            lplvdr->clrText = RGB(0, 0, 0);
            lplvdr->clrTextBk = RGB(235, 235, 235);
@@ -49,7 +50,7 @@
            lplvdr->clrTextBk = RGB(255, 255, 255);
            *pResult = CDRF_DODEFAULT;
        }
        */
        break;
    }
SourceCode/Bond/Servo/Model.cpp
@@ -8,6 +8,7 @@
#include "CGlassPool.h"
#include "TransferManager.h"
#include "RecipeManager.h"
#include "GlassLogDb.h"
CModel::CModel()
@@ -427,13 +428,15 @@
        m_hsmsPassive.setVariableValue("PJEndID", ((SERVO::CProcessJob*)pj)->id().c_str());
        m_hsmsPassive.requestEventReportSend_PJ_End();
    };
    masterListener.onPanelStart = [&](void* pMaster, void* pj) {
        m_hsmsPassive.setVariableValue("PanelStartID", ((SERVO::CGlass*)pj)->getID().c_str());
    masterListener.onPanelStart = [&](void* pMaster, void* pPanel) {
        m_hsmsPassive.setVariableValue("PanelStartID", ((SERVO::CGlass*)pPanel)->getID().c_str());
        m_hsmsPassive.requestEventReportSend_Panel_Start();
    };
    masterListener.onPanelEnd = [&](void* pMaster, void* pj) {
        m_hsmsPassive.setVariableValue("PanelEndID", ((SERVO::CGlass*)pj)->getID().c_str());
    masterListener.onPanelEnd = [&](void* pMaster, void* pPanel) {
        m_hsmsPassive.setVariableValue("PanelEndID", ((SERVO::CGlass*)pPanel)->getID().c_str());
        m_hsmsPassive.requestEventReportSend_Panel_End();
        auto& db = GlassLogDb::Instance();
        db.insertFromCGlass((*(SERVO::CGlass*)pPanel));
    };
    m_master.setListener(masterListener);
    m_master.setContinuousTransferCount(m_configuration.getContinuousTransferCount());
@@ -461,6 +464,11 @@
    alarmManager.readAlarmFile(szBuffer);
    // Glass数据库
    strLogDir.Format(_T("%s\\db\\process.db"), (LPTSTR)(LPCTSTR)m_strWorkDir);
    std::string path((LPTSTR)(LPCTSTR)strLogDir);
    GlassLogDb::Init(path);
    return 0;
}
SourceCode/Bond/Servo/Servo.rc
Binary files differ
SourceCode/Bond/Servo/Servo.vcxproj
@@ -231,9 +231,12 @@
    <ClInclude Include="CPagePortStatus.h" />
    <ClInclude Include="CPortStatusReport.h" />
    <ClInclude Include="CRobotTaskDlg.h" />
    <ClInclude Include="CSVData.h" />
    <ClInclude Include="CServoUtilsTool.h" />
    <ClInclude Include="CVariable.h" />
    <ClInclude Include="DeviceRecipeParamDlg.h" />
    <ClInclude Include="GlassJson.h" />
    <ClInclude Include="GlassLogDb.h" />
    <ClInclude Include="GridControl\CellRange.h" />
    <ClInclude Include="GridControl\GridCell.h" />
    <ClInclude Include="GridControl\GridCellBase.h" />
@@ -354,6 +357,8 @@
    <ClInclude Include="ServoDlg.h" />
    <ClInclude Include="ServoGraph.h" />
    <ClInclude Include="ServoMemDC.h" />
    <ClInclude Include="sqlite3.h" />
    <ClInclude Include="sqlite3ext.h" />
    <ClInclude Include="stdafx.h" />
    <ClInclude Include="SystemLogManager.h" />
    <ClInclude Include="SystemLogManagerDlg.h" />
@@ -401,9 +406,12 @@
    <ClCompile Include="CPagePortStatus.cpp" />
    <ClCompile Include="CPortStatusReport.cpp" />
    <ClCompile Include="CRobotTaskDlg.cpp" />
    <ClCompile Include="CSVData.cpp" />
    <ClCompile Include="CServoUtilsTool.cpp" />
    <ClCompile Include="CVariable.cpp" />
    <ClCompile Include="DeviceRecipeParamDlg.cpp" />
    <ClCompile Include="GlassJson.cpp" />
    <ClCompile Include="GlassLogDb.cpp" />
    <ClCompile Include="GridControl\GridCell.cpp" />
    <ClCompile Include="GridControl\GridCellBase.cpp" />
    <ClCompile Include="GridControl\GridCellButton.cpp" />
@@ -518,6 +526,10 @@
    <ClCompile Include="ServoDlg.cpp" />
    <ClCompile Include="ServoGraph.cpp" />
    <ClCompile Include="ServoMemDC.cpp" />
    <ClCompile Include="sqlite3.c">
      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
    </ClCompile>
    <ClCompile Include="stdafx.cpp">
      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
SourceCode/Bond/Servo/Servo.vcxproj.filters
@@ -193,6 +193,10 @@
      <Filter>JsonCpp</Filter>
    </ClCompile>
    <ClCompile Include="DeviceRecipeParamDlg.cpp" />
    <ClCompile Include="CSVData.cpp" />
    <ClCompile Include="CServoUtilsTool.cpp" />
    <ClCompile Include="GlassLogDb.cpp" />
    <ClCompile Include="sqlite3.c" />
  </ItemGroup>
  <ItemGroup>
    <ClInclude Include="AlarmManager.h" />
@@ -410,6 +414,11 @@
      <Filter>JsonCpp</Filter>
    </ClInclude>
    <ClInclude Include="DeviceRecipeParamDlg.h" />
    <ClInclude Include="CSVData.h" />
    <ClInclude Include="CServoUtilsTool.h" />
    <ClInclude Include="GlassLogDb.h" />
    <ClInclude Include="sqlite3.h" />
    <ClInclude Include="sqlite3ext.h" />
  </ItemGroup>
  <ItemGroup>
    <ResourceCompile Include="Servo.rc" />
SourceCode/Bond/Servo/ToolUnits.cpp
@@ -3,6 +3,7 @@
#include <chrono>
#include <memory>
#include <sstream>
#include <algorithm>
CToolUnits::CToolUnits()
@@ -337,3 +338,222 @@
        strOut = std::string(pszBuffer, nLength);
    }
}
// FILETIME(UTC) -> time_point
std::chrono::system_clock::time_point CToolUnits::FileTimeToTimePointUTC(const FILETIME& ftUtc) {
    ULARGE_INTEGER ull;
    ull.LowPart = ftUtc.dwLowDateTime;
    ull.HighPart = ftUtc.dwHighDateTime;
    // 1601-01-01 åˆ° 1970-01-01 çš„ 100ns tick
    constexpr unsigned long long EPOCH_DIFF_100NS = 116444736000000000ULL;
    unsigned long long t100 = ull.QuadPart - EPOCH_DIFF_100NS; // 1970以来 100ns
    // é¿å…æº¢å‡ºï¼š100ns -> us
    unsigned long long us = t100 / 10ULL;
    return std::chrono::system_clock::time_point(std::chrono::microseconds(us));
}
// ä»Ž CDateTimeCtrl å–本地时间并转为 UTC time_point
// è¿”回 false è¡¨ç¤ºæŽ§ä»¶æ˜¯â€œæ— å€¼â€(DTS_SHOWNONE / GDT_NONE) æˆ–转换失败
bool CToolUnits::GetCtrlTimeUtc(const CDateTimeCtrl& ctrl,
    std::chrono::system_clock::time_point& outUtc)
{
    SYSTEMTIME stLocal{};
    DWORD rc = const_cast<CDateTimeCtrl&>(ctrl).GetTime(&stLocal); // MFC åŽŸåž‹éœ€è¦éžconst
    if (rc != GDT_VALID) return false; // GDT_NONE
    SYSTEMTIME stUtc{};
    if (!TzSpecificLocalTimeToSystemTime(nullptr, &stLocal, &stUtc)) return false;
    FILETIME ftUtc{};
    if (!SystemTimeToFileTime(&stUtc, &ftUtc)) return false;
    outUtc = FileTimeToTimePointUTC(ftUtc);
    return true;
}
// è‹¥ä½ çš„æ—¥æœŸæŽ§ä»¶åªæ˜¾ç¤ºâ€œæ—¥æœŸä¸æ˜¾ç¤ºæ—¶é—´â€ï¼Œæƒ³æŠŠ From è°ƒæ•´åˆ° 00:00:00 / To è°ƒæ•´åˆ° 23:59:59.999:
bool CToolUnits::GetCtrlDateRangeUtc_StartOfDay(const CDateTimeCtrl& ctrl,
    std::chrono::system_clock::time_point& outUtc)
{
    SYSTEMTIME st{};
    DWORD rc = const_cast<CDateTimeCtrl&>(ctrl).GetTime(&st);
    if (rc != GDT_VALID) return false;
    st.wHour = 0; st.wMinute = 0; st.wSecond = 0; st.wMilliseconds = 0;
    SYSTEMTIME stUtc{};
    if (!TzSpecificLocalTimeToSystemTime(nullptr, &st, &stUtc)) return false;
    FILETIME ftUtc{}; if (!SystemTimeToFileTime(&stUtc, &ftUtc)) return false;
    outUtc = FileTimeToTimePointUTC(ftUtc);
    return true;
}
bool CToolUnits::GetCtrlDateRangeUtc_EndOfDay(const CDateTimeCtrl& ctrl,
    std::chrono::system_clock::time_point& outUtc)
{
    SYSTEMTIME st{};
    DWORD rc = const_cast<CDateTimeCtrl&>(ctrl).GetTime(&st);
    if (rc != GDT_VALID) return false;
    st.wHour = 23; st.wMinute = 59; st.wSecond = 59; st.wMilliseconds = 999;
    SYSTEMTIME stUtc{};
    if (!TzSpecificLocalTimeToSystemTime(nullptr, &st, &stUtc)) return false;
    FILETIME ftUtc{}; if (!SystemTimeToFileTime(&stUtc, &ftUtc)) return false;
    outUtc = FileTimeToTimePointUTC(ftUtc);
    return true;
}
// ---------- æœ¬åœ° SYSTEMTIME -> UTC time_point ----------
std::chrono::system_clock::time_point CToolUnits::LocalSTtoUtcTP(const SYSTEMTIME& stLocal) {
    SYSTEMTIME stUtc{};
    if (!TzSpecificLocalTimeToSystemTime(nullptr, &stLocal, &stUtc))
        throw std::runtime_error("TzSpecificLocalTimeToSystemTime failed");
    FILETIME ftUtc{};
    if (!SystemTimeToFileTime(&stUtc, &ftUtc))
        throw std::runtime_error("SystemTimeToFileTime failed");
    return FileTimeToTimePointUTC(ftUtc);
}
// ç”Ÿæˆæœ¬åœ° SYSTEMTIME
SYSTEMTIME CToolUnits::MakeLocalST(int y, int m, int d, int hh, int mi, int ss, int ms) {
    SYSTEMTIME st{};
    st.wYear = (WORD)y; st.wMonth = (WORD)m; st.wDay = (WORD)d;
    st.wHour = (WORD)hh; st.wMinute = (WORD)mi; st.wSecond = (WORD)ss; st.wMilliseconds = (WORD)ms;
    return st;
}
// é—°å¹´ / æœˆå¤©æ•°
bool CToolUnits::IsLeap(int y) {
    return ((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0);
}
int CToolUnits::DaysInMonth(int y, int m) {
    static const int d[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
    return (m == 2) ? d[m] + (IsLeap(y) ? 1 : 0) : d[m];
}
// æœ¬åœ°â€œä»Šå¤©â€çš„年月日
void CToolUnits::GetTodayYMD_Local(int& y, int& m, int& d) {
    SYSTEMTIME now{}; GetLocalTime(&now);
    y = now.wYear; m = now.wMonth; d = now.wDay;
}
// æœ¬åœ°æ—¥åŽ†ä¸Šå‡ n å¤©ï¼ˆä»æ˜¯æœ¬åœ° Y/M/D)
void CToolUnits::LocalCalendarMinusDays(int& y, int& m, int& d, int nDays) {
    std::tm t{};
    t.tm_year = y - 1900; t.tm_mon = m - 1; t.tm_mday = d;
    t.tm_hour = 0; t.tm_min = 0; t.tm_sec = 0;
    t.tm_mday -= nDays;
    (void)std::mktime(&t); // æŒ‰æœ¬åœ°æ—¶åŒºå½’一化
    y = t.tm_year + 1900; m = t.tm_mon + 1; d = t.tm_mday;
}
// ====== æ ¸å¿ƒï¼šè®¡ç®—预设区间的 UTC [from, to] ======
// è¯­ä¹‰ï¼š
//  - Today     : æœ¬åœ°â€œä»Šå¤©â€00:00:00.000 ï½ž ä»Šå¤© 23:59:59.999
//  - Last7Days : æœ¬åœ°â€œ6 å¤©å‰â€00:00:00.000 ï½ž ä»Šå¤© 23:59:59.999(共 7 ä¸ªè‡ªç„¶æ—¥ï¼Œå«ä»Šå¤©ï¼‰
//  - ThisMonth : æœ¬åœ°â€œå½“月 1 å·â€00:00:00.000 ï½ž æœ¬æœˆæœ« 23:59:59.999
//  - ThisYear  : æœ¬åœ°â€œå½“å¹´ 1/1”00:00:00.000 ï½ž å½“年末 23:59:59.999
std::pair<std::chrono::system_clock::time_point,
    std::chrono::system_clock::time_point>
    CToolUnits::CalcQuickRangeUtc(QuickRange r)
{
    int y, m, d;
    GetTodayYMD_Local(y, m, d);
    SYSTEMTIME stFromLocal{}, stToLocal{};
    switch (r) {
    case QuickRange::Today:
        stFromLocal = MakeLocalST(y, m, d, 0, 0, 0, 0);
        stToLocal = MakeLocalST(y, m, d, 23, 59, 59, 999);
        break;
    case QuickRange::Last7Days: {
        int y2 = y, m2 = m, d2 = d;
        LocalCalendarMinusDays(y2, m2, d2, 6);
        stFromLocal = MakeLocalST(y2, m2, d2, 0, 0, 0, 0);
        stToLocal = MakeLocalST(y, m, d, 23, 59, 59, 999);
        break;
    }
    case QuickRange::ThisMonth: {
        int lastDay = DaysInMonth(y, m);
        stFromLocal = MakeLocalST(y, m, 1, 0, 0, 0, 0);
        stToLocal = MakeLocalST(y, m, lastDay, 23, 59, 59, 999);
        break;
    }
    case QuickRange::ThisYear:
        stFromLocal = MakeLocalST(y, 1, 1, 0, 0, 0, 0);
        stToLocal = MakeLocalST(y, 12, DaysInMonth(y, 12), 23, 59, 59, 999);
        break;
    default:
        throw std::invalid_argument("CalcQuickRangeUtc: unsupported range");
    }
    auto fromUtc = LocalSTtoUtcTP(stFromLocal);
    auto toUtc = LocalSTtoUtcTP(stToLocal);
    return { fromUtc, toUtc };
}
// å°å·¥å…·ï¼šASCII ä¸åŒºåˆ†å¤§å°å†™åŒ…含(中文等非 ASCII ä¸å—影响,但也不需要大小写转换)
bool CToolUnits::containsCI(const std::string& hay, const std::string& needle) {
    if (needle.empty()) return true;
    auto toLower = [](std::string s) { std::transform(s.begin(), s.end(), s.begin(),
        [](unsigned char c) { return (char)std::tolower(c); }); return s; };
    std::string h = toLower(hay), n = toLower(needle);
    return h.find(n) != std::string::npos;
}
// ------- æœ¬åœ°æ—¶é—´ -------
std::string CToolUnits::TimePointToLocalString(const std::optional<TP>& tp,
    const char* fmt/* = "%Y-%m-%d %H:%M:%S"*/)
{
    if (!tp) return {};
    std::time_t t = std::chrono::system_clock::to_time_t(*tp);
    std::tm tm{};
#if defined(_WIN32)
    localtime_s(&tm, &t);
#else
    localtime_r(&t, &tm);
#endif
    char buf[64]{};
    std::strftime(buf, sizeof(buf), fmt, &tm);
    return buf;
}
// ------- UTC æ—¶é—´ -------
std::string CToolUnits::TimePointToUtcString(const std::optional<TP>& tp,
    const char* fmt/* = "%Y-%m-%d %H:%M:%S"*/)
{
    if (!tp) return {};
    std::time_t t = std::chrono::system_clock::to_time_t(*tp);
    std::tm tm{};
#if defined(_WIN32)
    gmtime_s(&tm, &t);
#else
    gmtime_r(&t, &tm);
#endif
    char buf[64]{};
    std::strftime(buf, sizeof(buf), fmt, &tm);
    return buf;
}
std::string CToolUnits::TimePointToLocalStringMs(const std::optional<TP>& tp)
{
    if (!tp) return {};
    using namespace std::chrono;
    auto ms_since_epoch = duration_cast<milliseconds>(tp->time_since_epoch());
    auto ms = static_cast<int>(ms_since_epoch.count() % 1000);
    std::time_t t = system_clock::to_time_t(*tp);
    std::tm tm{};
#if defined(_WIN32)
    localtime_s(&tm, &t);
#else
    localtime_r(&t, &tm);
#endif
    char date[32]{};
    std::strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", &tm);
    char out[40]{};
    std::snprintf(out, sizeof(out), "%s.%03d", date, ms);
    return out;
}
SourceCode/Bond/Servo/ToolUnits.h
@@ -1,6 +1,11 @@
#pragma once
#include <string>
#include <chrono>
#include <optional>
#include <utility>
enum class QuickRange { Today, Last7Days, ThisMonth, ThisYear };
using TP = std::chrono::system_clock::time_point;
class CToolUnits
{
public:
@@ -32,5 +37,27 @@
    static bool startsWith(const std::string& str, const std::string& prefix);
    static std::string& toHexString(int value, std::string& strOut);
    static void convertString(const char* pszBuffer, int size, std::string& strOut);
    static inline std::chrono::system_clock::time_point FileTimeToTimePointUTC(const FILETIME& ftUtc);
    static bool GetCtrlTimeUtc(const CDateTimeCtrl& ctrl,
        std::chrono::system_clock::time_point& outUtc);
    static bool GetCtrlDateRangeUtc_StartOfDay(const CDateTimeCtrl& ctrl,
        std::chrono::system_clock::time_point& outUtc);
    static bool GetCtrlDateRangeUtc_EndOfDay(const CDateTimeCtrl& ctrl,
        std::chrono::system_clock::time_point& outUtc);
    static std::chrono::system_clock::time_point CToolUnits::LocalSTtoUtcTP(const SYSTEMTIME& stLocal);
    static SYSTEMTIME CToolUnits::MakeLocalST(int y, int m, int d, int hh, int mi, int ss, int ms);
    static bool IsLeap(int y);
    static int DaysInMonth(int y, int m);
    static void GetTodayYMD_Local(int& y, int& m, int& d);
    static void LocalCalendarMinusDays(int& y, int& m, int& d, int nDays);
    static std::pair<std::chrono::system_clock::time_point,
        std::chrono::system_clock::time_point>
        CalcQuickRangeUtc(QuickRange r);
    static bool containsCI(const std::string& hay, const std::string& needle);
    static std::string TimePointToLocalString(const std::optional<TP>& tp,
        const char* fmt = "%Y-%m-%d %H:%M:%S");
    static std::string TimePointToUtcString(const std::optional<TP>& tp,
        const char* fmt = "%Y-%m-%d %H:%M:%S");
    static std::string TimePointToLocalStringMs(const std::optional<TP>& tp);
};
SourceCode/Bond/Servo/resource.h
Binary files differ
SourceCode/Bond/Servo/sqlite3.c
¶Ô±ÈÐÂÎļþ
ÎļþÌ«´ó
SourceCode/Bond/Servo/sqlite3.h
¶Ô±ÈÐÂÎļþ
ÎļþÌ«´ó
SourceCode/Bond/Servo/sqlite3ext.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,719 @@
/*
** 2006 June 7
**
** The author disclaims copyright to this source code.  In place of
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** This header file defines the SQLite interface for use by
** shared libraries that want to be imported as extensions into
** an SQLite instance.  Shared libraries that intend to be loaded
** as extensions by SQLite should #include this file instead of
** sqlite3.h.
*/
#ifndef SQLITE3EXT_H
#define SQLITE3EXT_H
#include "sqlite3.h"
/*
** The following structure holds pointers to all of the SQLite API
** routines.
**
** WARNING:  In order to maintain backwards compatibility, add new
** interfaces to the end of this structure only.  If you insert new
** interfaces in the middle of this structure, then older different
** versions of SQLite will not be able to load each other's shared
** libraries!
*/
struct sqlite3_api_routines {
  void * (*aggregate_context)(sqlite3_context*,int nBytes);
  int  (*aggregate_count)(sqlite3_context*);
  int  (*bind_blob)(sqlite3_stmt*,int,const void*,int n,void(*)(void*));
  int  (*bind_double)(sqlite3_stmt*,int,double);
  int  (*bind_int)(sqlite3_stmt*,int,int);
  int  (*bind_int64)(sqlite3_stmt*,int,sqlite_int64);
  int  (*bind_null)(sqlite3_stmt*,int);
  int  (*bind_parameter_count)(sqlite3_stmt*);
  int  (*bind_parameter_index)(sqlite3_stmt*,const char*zName);
  const char * (*bind_parameter_name)(sqlite3_stmt*,int);
  int  (*bind_text)(sqlite3_stmt*,int,const char*,int n,void(*)(void*));
  int  (*bind_text16)(sqlite3_stmt*,int,const void*,int,void(*)(void*));
  int  (*bind_value)(sqlite3_stmt*,int,const sqlite3_value*);
  int  (*busy_handler)(sqlite3*,int(*)(void*,int),void*);
  int  (*busy_timeout)(sqlite3*,int ms);
  int  (*changes)(sqlite3*);
  int  (*close)(sqlite3*);
  int  (*collation_needed)(sqlite3*,void*,void(*)(void*,sqlite3*,
                           int eTextRep,const char*));
  int  (*collation_needed16)(sqlite3*,void*,void(*)(void*,sqlite3*,
                             int eTextRep,const void*));
  const void * (*column_blob)(sqlite3_stmt*,int iCol);
  int  (*column_bytes)(sqlite3_stmt*,int iCol);
  int  (*column_bytes16)(sqlite3_stmt*,int iCol);
  int  (*column_count)(sqlite3_stmt*pStmt);
  const char * (*column_database_name)(sqlite3_stmt*,int);
  const void * (*column_database_name16)(sqlite3_stmt*,int);
  const char * (*column_decltype)(sqlite3_stmt*,int i);
  const void * (*column_decltype16)(sqlite3_stmt*,int);
  double  (*column_double)(sqlite3_stmt*,int iCol);
  int  (*column_int)(sqlite3_stmt*,int iCol);
  sqlite_int64  (*column_int64)(sqlite3_stmt*,int iCol);
  const char * (*column_name)(sqlite3_stmt*,int);
  const void * (*column_name16)(sqlite3_stmt*,int);
  const char * (*column_origin_name)(sqlite3_stmt*,int);
  const void * (*column_origin_name16)(sqlite3_stmt*,int);
  const char * (*column_table_name)(sqlite3_stmt*,int);
  const void * (*column_table_name16)(sqlite3_stmt*,int);
  const unsigned char * (*column_text)(sqlite3_stmt*,int iCol);
  const void * (*column_text16)(sqlite3_stmt*,int iCol);
  int  (*column_type)(sqlite3_stmt*,int iCol);
  sqlite3_value* (*column_value)(sqlite3_stmt*,int iCol);
  void * (*commit_hook)(sqlite3*,int(*)(void*),void*);
  int  (*complete)(const char*sql);
  int  (*complete16)(const void*sql);
  int  (*create_collation)(sqlite3*,const char*,int,void*,
                           int(*)(void*,int,const void*,int,const void*));
  int  (*create_collation16)(sqlite3*,const void*,int,void*,
                             int(*)(void*,int,const void*,int,const void*));
  int  (*create_function)(sqlite3*,const char*,int,int,void*,
                          void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
                          void (*xStep)(sqlite3_context*,int,sqlite3_value**),
                          void (*xFinal)(sqlite3_context*));
  int  (*create_function16)(sqlite3*,const void*,int,int,void*,
                            void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
                            void (*xStep)(sqlite3_context*,int,sqlite3_value**),
                            void (*xFinal)(sqlite3_context*));
  int (*create_module)(sqlite3*,const char*,const sqlite3_module*,void*);
  int  (*data_count)(sqlite3_stmt*pStmt);
  sqlite3 * (*db_handle)(sqlite3_stmt*);
  int (*declare_vtab)(sqlite3*,const char*);
  int  (*enable_shared_cache)(int);
  int  (*errcode)(sqlite3*db);
  const char * (*errmsg)(sqlite3*);
  const void * (*errmsg16)(sqlite3*);
  int  (*exec)(sqlite3*,const char*,sqlite3_callback,void*,char**);
  int  (*expired)(sqlite3_stmt*);
  int  (*finalize)(sqlite3_stmt*pStmt);
  void  (*free)(void*);
  void  (*free_table)(char**result);
  int  (*get_autocommit)(sqlite3*);
  void * (*get_auxdata)(sqlite3_context*,int);
  int  (*get_table)(sqlite3*,const char*,char***,int*,int*,char**);
  int  (*global_recover)(void);
  void  (*interruptx)(sqlite3*);
  sqlite_int64  (*last_insert_rowid)(sqlite3*);
  const char * (*libversion)(void);
  int  (*libversion_number)(void);
  void *(*malloc)(int);
  char * (*mprintf)(const char*,...);
  int  (*open)(const char*,sqlite3**);
  int  (*open16)(const void*,sqlite3**);
  int  (*prepare)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
  int  (*prepare16)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
  void * (*profile)(sqlite3*,void(*)(void*,const char*,sqlite_uint64),void*);
  void  (*progress_handler)(sqlite3*,int,int(*)(void*),void*);
  void *(*realloc)(void*,int);
  int  (*reset)(sqlite3_stmt*pStmt);
  void  (*result_blob)(sqlite3_context*,const void*,int,void(*)(void*));
  void  (*result_double)(sqlite3_context*,double);
  void  (*result_error)(sqlite3_context*,const char*,int);
  void  (*result_error16)(sqlite3_context*,const void*,int);
  void  (*result_int)(sqlite3_context*,int);
  void  (*result_int64)(sqlite3_context*,sqlite_int64);
  void  (*result_null)(sqlite3_context*);
  void  (*result_text)(sqlite3_context*,const char*,int,void(*)(void*));
  void  (*result_text16)(sqlite3_context*,const void*,int,void(*)(void*));
  void  (*result_text16be)(sqlite3_context*,const void*,int,void(*)(void*));
  void  (*result_text16le)(sqlite3_context*,const void*,int,void(*)(void*));
  void  (*result_value)(sqlite3_context*,sqlite3_value*);
  void * (*rollback_hook)(sqlite3*,void(*)(void*),void*);
  int  (*set_authorizer)(sqlite3*,int(*)(void*,int,const char*,const char*,
                         const char*,const char*),void*);
  void  (*set_auxdata)(sqlite3_context*,int,void*,void (*)(void*));
  char * (*xsnprintf)(int,char*,const char*,...);
  int  (*step)(sqlite3_stmt*);
  int  (*table_column_metadata)(sqlite3*,const char*,const char*,const char*,
                                char const**,char const**,int*,int*,int*);
  void  (*thread_cleanup)(void);
  int  (*total_changes)(sqlite3*);
  void * (*trace)(sqlite3*,void(*xTrace)(void*,const char*),void*);
  int  (*transfer_bindings)(sqlite3_stmt*,sqlite3_stmt*);
  void * (*update_hook)(sqlite3*,void(*)(void*,int ,char const*,char const*,
                                         sqlite_int64),void*);
  void * (*user_data)(sqlite3_context*);
  const void * (*value_blob)(sqlite3_value*);
  int  (*value_bytes)(sqlite3_value*);
  int  (*value_bytes16)(sqlite3_value*);
  double  (*value_double)(sqlite3_value*);
  int  (*value_int)(sqlite3_value*);
  sqlite_int64  (*value_int64)(sqlite3_value*);
  int  (*value_numeric_type)(sqlite3_value*);
  const unsigned char * (*value_text)(sqlite3_value*);
  const void * (*value_text16)(sqlite3_value*);
  const void * (*value_text16be)(sqlite3_value*);
  const void * (*value_text16le)(sqlite3_value*);
  int  (*value_type)(sqlite3_value*);
  char *(*vmprintf)(const char*,va_list);
  /* Added ??? */
  int (*overload_function)(sqlite3*, const char *zFuncName, int nArg);
  /* Added by 3.3.13 */
  int (*prepare_v2)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
  int (*prepare16_v2)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
  int (*clear_bindings)(sqlite3_stmt*);
  /* Added by 3.4.1 */
  int (*create_module_v2)(sqlite3*,const char*,const sqlite3_module*,void*,
                          void (*xDestroy)(void *));
  /* Added by 3.5.0 */
  int (*bind_zeroblob)(sqlite3_stmt*,int,int);
  int (*blob_bytes)(sqlite3_blob*);
  int (*blob_close)(sqlite3_blob*);
  int (*blob_open)(sqlite3*,const char*,const char*,const char*,sqlite3_int64,
                   int,sqlite3_blob**);
  int (*blob_read)(sqlite3_blob*,void*,int,int);
  int (*blob_write)(sqlite3_blob*,const void*,int,int);
  int (*create_collation_v2)(sqlite3*,const char*,int,void*,
                             int(*)(void*,int,const void*,int,const void*),
                             void(*)(void*));
  int (*file_control)(sqlite3*,const char*,int,void*);
  sqlite3_int64 (*memory_highwater)(int);
  sqlite3_int64 (*memory_used)(void);
  sqlite3_mutex *(*mutex_alloc)(int);
  void (*mutex_enter)(sqlite3_mutex*);
  void (*mutex_free)(sqlite3_mutex*);
  void (*mutex_leave)(sqlite3_mutex*);
  int (*mutex_try)(sqlite3_mutex*);
  int (*open_v2)(const char*,sqlite3**,int,const char*);
  int (*release_memory)(int);
  void (*result_error_nomem)(sqlite3_context*);
  void (*result_error_toobig)(sqlite3_context*);
  int (*sleep)(int);
  void (*soft_heap_limit)(int);
  sqlite3_vfs *(*vfs_find)(const char*);
  int (*vfs_register)(sqlite3_vfs*,int);
  int (*vfs_unregister)(sqlite3_vfs*);
  int (*xthreadsafe)(void);
  void (*result_zeroblob)(sqlite3_context*,int);
  void (*result_error_code)(sqlite3_context*,int);
  int (*test_control)(int, ...);
  void (*randomness)(int,void*);
  sqlite3 *(*context_db_handle)(sqlite3_context*);
  int (*extended_result_codes)(sqlite3*,int);
  int (*limit)(sqlite3*,int,int);
  sqlite3_stmt *(*next_stmt)(sqlite3*,sqlite3_stmt*);
  const char *(*sql)(sqlite3_stmt*);
  int (*status)(int,int*,int*,int);
  int (*backup_finish)(sqlite3_backup*);
  sqlite3_backup *(*backup_init)(sqlite3*,const char*,sqlite3*,const char*);
  int (*backup_pagecount)(sqlite3_backup*);
  int (*backup_remaining)(sqlite3_backup*);
  int (*backup_step)(sqlite3_backup*,int);
  const char *(*compileoption_get)(int);
  int (*compileoption_used)(const char*);
  int (*create_function_v2)(sqlite3*,const char*,int,int,void*,
                            void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
                            void (*xStep)(sqlite3_context*,int,sqlite3_value**),
                            void (*xFinal)(sqlite3_context*),
                            void(*xDestroy)(void*));
  int (*db_config)(sqlite3*,int,...);
  sqlite3_mutex *(*db_mutex)(sqlite3*);
  int (*db_status)(sqlite3*,int,int*,int*,int);
  int (*extended_errcode)(sqlite3*);
  void (*log)(int,const char*,...);
  sqlite3_int64 (*soft_heap_limit64)(sqlite3_int64);
  const char *(*sourceid)(void);
  int (*stmt_status)(sqlite3_stmt*,int,int);
  int (*strnicmp)(const char*,const char*,int);
  int (*unlock_notify)(sqlite3*,void(*)(void**,int),void*);
  int (*wal_autocheckpoint)(sqlite3*,int);
  int (*wal_checkpoint)(sqlite3*,const char*);
  void *(*wal_hook)(sqlite3*,int(*)(void*,sqlite3*,const char*,int),void*);
  int (*blob_reopen)(sqlite3_blob*,sqlite3_int64);
  int (*vtab_config)(sqlite3*,int op,...);
  int (*vtab_on_conflict)(sqlite3*);
  /* Version 3.7.16 and later */
  int (*close_v2)(sqlite3*);
  const char *(*db_filename)(sqlite3*,const char*);
  int (*db_readonly)(sqlite3*,const char*);
  int (*db_release_memory)(sqlite3*);
  const char *(*errstr)(int);
  int (*stmt_busy)(sqlite3_stmt*);
  int (*stmt_readonly)(sqlite3_stmt*);
  int (*stricmp)(const char*,const char*);
  int (*uri_boolean)(const char*,const char*,int);
  sqlite3_int64 (*uri_int64)(const char*,const char*,sqlite3_int64);
  const char *(*uri_parameter)(const char*,const char*);
  char *(*xvsnprintf)(int,char*,const char*,va_list);
  int (*wal_checkpoint_v2)(sqlite3*,const char*,int,int*,int*);
  /* Version 3.8.7 and later */
  int (*auto_extension)(void(*)(void));
  int (*bind_blob64)(sqlite3_stmt*,int,const void*,sqlite3_uint64,
                     void(*)(void*));
  int (*bind_text64)(sqlite3_stmt*,int,const char*,sqlite3_uint64,
                      void(*)(void*),unsigned char);
  int (*cancel_auto_extension)(void(*)(void));
  int (*load_extension)(sqlite3*,const char*,const char*,char**);
  void *(*malloc64)(sqlite3_uint64);
  sqlite3_uint64 (*msize)(void*);
  void *(*realloc64)(void*,sqlite3_uint64);
  void (*reset_auto_extension)(void);
  void (*result_blob64)(sqlite3_context*,const void*,sqlite3_uint64,
                        void(*)(void*));
  void (*result_text64)(sqlite3_context*,const char*,sqlite3_uint64,
                         void(*)(void*), unsigned char);
  int (*strglob)(const char*,const char*);
  /* Version 3.8.11 and later */
  sqlite3_value *(*value_dup)(const sqlite3_value*);
  void (*value_free)(sqlite3_value*);
  int (*result_zeroblob64)(sqlite3_context*,sqlite3_uint64);
  int (*bind_zeroblob64)(sqlite3_stmt*, int, sqlite3_uint64);
  /* Version 3.9.0 and later */
  unsigned int (*value_subtype)(sqlite3_value*);
  void (*result_subtype)(sqlite3_context*,unsigned int);
  /* Version 3.10.0 and later */
  int (*status64)(int,sqlite3_int64*,sqlite3_int64*,int);
  int (*strlike)(const char*,const char*,unsigned int);
  int (*db_cacheflush)(sqlite3*);
  /* Version 3.12.0 and later */
  int (*system_errno)(sqlite3*);
  /* Version 3.14.0 and later */
  int (*trace_v2)(sqlite3*,unsigned,int(*)(unsigned,void*,void*,void*),void*);
  char *(*expanded_sql)(sqlite3_stmt*);
  /* Version 3.18.0 and later */
  void (*set_last_insert_rowid)(sqlite3*,sqlite3_int64);
  /* Version 3.20.0 and later */
  int (*prepare_v3)(sqlite3*,const char*,int,unsigned int,
                    sqlite3_stmt**,const char**);
  int (*prepare16_v3)(sqlite3*,const void*,int,unsigned int,
                      sqlite3_stmt**,const void**);
  int (*bind_pointer)(sqlite3_stmt*,int,void*,const char*,void(*)(void*));
  void (*result_pointer)(sqlite3_context*,void*,const char*,void(*)(void*));
  void *(*value_pointer)(sqlite3_value*,const char*);
  int (*vtab_nochange)(sqlite3_context*);
  int (*value_nochange)(sqlite3_value*);
  const char *(*vtab_collation)(sqlite3_index_info*,int);
  /* Version 3.24.0 and later */
  int (*keyword_count)(void);
  int (*keyword_name)(int,const char**,int*);
  int (*keyword_check)(const char*,int);
  sqlite3_str *(*str_new)(sqlite3*);
  char *(*str_finish)(sqlite3_str*);
  void (*str_appendf)(sqlite3_str*, const char *zFormat, ...);
  void (*str_vappendf)(sqlite3_str*, const char *zFormat, va_list);
  void (*str_append)(sqlite3_str*, const char *zIn, int N);
  void (*str_appendall)(sqlite3_str*, const char *zIn);
  void (*str_appendchar)(sqlite3_str*, int N, char C);
  void (*str_reset)(sqlite3_str*);
  int (*str_errcode)(sqlite3_str*);
  int (*str_length)(sqlite3_str*);
  char *(*str_value)(sqlite3_str*);
  /* Version 3.25.0 and later */
  int (*create_window_function)(sqlite3*,const char*,int,int,void*,
                            void (*xStep)(sqlite3_context*,int,sqlite3_value**),
                            void (*xFinal)(sqlite3_context*),
                            void (*xValue)(sqlite3_context*),
                            void (*xInv)(sqlite3_context*,int,sqlite3_value**),
                            void(*xDestroy)(void*));
  /* Version 3.26.0 and later */
  const char *(*normalized_sql)(sqlite3_stmt*);
  /* Version 3.28.0 and later */
  int (*stmt_isexplain)(sqlite3_stmt*);
  int (*value_frombind)(sqlite3_value*);
  /* Version 3.30.0 and later */
  int (*drop_modules)(sqlite3*,const char**);
  /* Version 3.31.0 and later */
  sqlite3_int64 (*hard_heap_limit64)(sqlite3_int64);
  const char *(*uri_key)(const char*,int);
  const char *(*filename_database)(const char*);
  const char *(*filename_journal)(const char*);
  const char *(*filename_wal)(const char*);
  /* Version 3.32.0 and later */
  const char *(*create_filename)(const char*,const char*,const char*,
                           int,const char**);
  void (*free_filename)(const char*);
  sqlite3_file *(*database_file_object)(const char*);
  /* Version 3.34.0 and later */
  int (*txn_state)(sqlite3*,const char*);
  /* Version 3.36.1 and later */
  sqlite3_int64 (*changes64)(sqlite3*);
  sqlite3_int64 (*total_changes64)(sqlite3*);
  /* Version 3.37.0 and later */
  int (*autovacuum_pages)(sqlite3*,
     unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int),
     void*, void(*)(void*));
  /* Version 3.38.0 and later */
  int (*error_offset)(sqlite3*);
  int (*vtab_rhs_value)(sqlite3_index_info*,int,sqlite3_value**);
  int (*vtab_distinct)(sqlite3_index_info*);
  int (*vtab_in)(sqlite3_index_info*,int,int);
  int (*vtab_in_first)(sqlite3_value*,sqlite3_value**);
  int (*vtab_in_next)(sqlite3_value*,sqlite3_value**);
  /* Version 3.39.0 and later */
  int (*deserialize)(sqlite3*,const char*,unsigned char*,
                     sqlite3_int64,sqlite3_int64,unsigned);
  unsigned char *(*serialize)(sqlite3*,const char *,sqlite3_int64*,
                              unsigned int);
  const char *(*db_name)(sqlite3*,int);
  /* Version 3.40.0 and later */
  int (*value_encoding)(sqlite3_value*);
  /* Version 3.41.0 and later */
  int (*is_interrupted)(sqlite3*);
  /* Version 3.43.0 and later */
  int (*stmt_explain)(sqlite3_stmt*,int);
  /* Version 3.44.0 and later */
  void *(*get_clientdata)(sqlite3*,const char*);
  int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*));
};
/*
** This is the function signature used for all extension entry points.  It
** is also defined in the file "loadext.c".
*/
typedef int (*sqlite3_loadext_entry)(
  sqlite3 *db,                       /* Handle to the database. */
  char **pzErrMsg,                   /* Used to set error string on failure. */
  const sqlite3_api_routines *pThunk /* Extension API function pointers. */
);
/*
** The following macros redefine the API routines so that they are
** redirected through the global sqlite3_api structure.
**
** This header file is also used by the loadext.c source file
** (part of the main SQLite library - not an extension) so that
** it can get access to the sqlite3_api_routines structure
** definition.  But the main library does not want to redefine
** the API.  So the redefinition macros are only valid if the
** SQLITE_CORE macros is undefined.
*/
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
#define sqlite3_aggregate_context      sqlite3_api->aggregate_context
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_aggregate_count        sqlite3_api->aggregate_count
#endif
#define sqlite3_bind_blob              sqlite3_api->bind_blob
#define sqlite3_bind_double            sqlite3_api->bind_double
#define sqlite3_bind_int               sqlite3_api->bind_int
#define sqlite3_bind_int64             sqlite3_api->bind_int64
#define sqlite3_bind_null              sqlite3_api->bind_null
#define sqlite3_bind_parameter_count   sqlite3_api->bind_parameter_count
#define sqlite3_bind_parameter_index   sqlite3_api->bind_parameter_index
#define sqlite3_bind_parameter_name    sqlite3_api->bind_parameter_name
#define sqlite3_bind_text              sqlite3_api->bind_text
#define sqlite3_bind_text16            sqlite3_api->bind_text16
#define sqlite3_bind_value             sqlite3_api->bind_value
#define sqlite3_busy_handler           sqlite3_api->busy_handler
#define sqlite3_busy_timeout           sqlite3_api->busy_timeout
#define sqlite3_changes                sqlite3_api->changes
#define sqlite3_close                  sqlite3_api->close
#define sqlite3_collation_needed       sqlite3_api->collation_needed
#define sqlite3_collation_needed16     sqlite3_api->collation_needed16
#define sqlite3_column_blob            sqlite3_api->column_blob
#define sqlite3_column_bytes           sqlite3_api->column_bytes
#define sqlite3_column_bytes16         sqlite3_api->column_bytes16
#define sqlite3_column_count           sqlite3_api->column_count
#define sqlite3_column_database_name   sqlite3_api->column_database_name
#define sqlite3_column_database_name16 sqlite3_api->column_database_name16
#define sqlite3_column_decltype        sqlite3_api->column_decltype
#define sqlite3_column_decltype16      sqlite3_api->column_decltype16
#define sqlite3_column_double          sqlite3_api->column_double
#define sqlite3_column_int             sqlite3_api->column_int
#define sqlite3_column_int64           sqlite3_api->column_int64
#define sqlite3_column_name            sqlite3_api->column_name
#define sqlite3_column_name16          sqlite3_api->column_name16
#define sqlite3_column_origin_name     sqlite3_api->column_origin_name
#define sqlite3_column_origin_name16   sqlite3_api->column_origin_name16
#define sqlite3_column_table_name      sqlite3_api->column_table_name
#define sqlite3_column_table_name16    sqlite3_api->column_table_name16
#define sqlite3_column_text            sqlite3_api->column_text
#define sqlite3_column_text16          sqlite3_api->column_text16
#define sqlite3_column_type            sqlite3_api->column_type
#define sqlite3_column_value           sqlite3_api->column_value
#define sqlite3_commit_hook            sqlite3_api->commit_hook
#define sqlite3_complete               sqlite3_api->complete
#define sqlite3_complete16             sqlite3_api->complete16
#define sqlite3_create_collation       sqlite3_api->create_collation
#define sqlite3_create_collation16     sqlite3_api->create_collation16
#define sqlite3_create_function        sqlite3_api->create_function
#define sqlite3_create_function16      sqlite3_api->create_function16
#define sqlite3_create_module          sqlite3_api->create_module
#define sqlite3_create_module_v2       sqlite3_api->create_module_v2
#define sqlite3_data_count             sqlite3_api->data_count
#define sqlite3_db_handle              sqlite3_api->db_handle
#define sqlite3_declare_vtab           sqlite3_api->declare_vtab
#define sqlite3_enable_shared_cache    sqlite3_api->enable_shared_cache
#define sqlite3_errcode                sqlite3_api->errcode
#define sqlite3_errmsg                 sqlite3_api->errmsg
#define sqlite3_errmsg16               sqlite3_api->errmsg16
#define sqlite3_exec                   sqlite3_api->exec
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_expired                sqlite3_api->expired
#endif
#define sqlite3_finalize               sqlite3_api->finalize
#define sqlite3_free                   sqlite3_api->free
#define sqlite3_free_table             sqlite3_api->free_table
#define sqlite3_get_autocommit         sqlite3_api->get_autocommit
#define sqlite3_get_auxdata            sqlite3_api->get_auxdata
#define sqlite3_get_table              sqlite3_api->get_table
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_global_recover         sqlite3_api->global_recover
#endif
#define sqlite3_interrupt              sqlite3_api->interruptx
#define sqlite3_last_insert_rowid      sqlite3_api->last_insert_rowid
#define sqlite3_libversion             sqlite3_api->libversion
#define sqlite3_libversion_number      sqlite3_api->libversion_number
#define sqlite3_malloc                 sqlite3_api->malloc
#define sqlite3_mprintf                sqlite3_api->mprintf
#define sqlite3_open                   sqlite3_api->open
#define sqlite3_open16                 sqlite3_api->open16
#define sqlite3_prepare                sqlite3_api->prepare
#define sqlite3_prepare16              sqlite3_api->prepare16
#define sqlite3_prepare_v2             sqlite3_api->prepare_v2
#define sqlite3_prepare16_v2           sqlite3_api->prepare16_v2
#define sqlite3_profile                sqlite3_api->profile
#define sqlite3_progress_handler       sqlite3_api->progress_handler
#define sqlite3_realloc                sqlite3_api->realloc
#define sqlite3_reset                  sqlite3_api->reset
#define sqlite3_result_blob            sqlite3_api->result_blob
#define sqlite3_result_double          sqlite3_api->result_double
#define sqlite3_result_error           sqlite3_api->result_error
#define sqlite3_result_error16         sqlite3_api->result_error16
#define sqlite3_result_int             sqlite3_api->result_int
#define sqlite3_result_int64           sqlite3_api->result_int64
#define sqlite3_result_null            sqlite3_api->result_null
#define sqlite3_result_text            sqlite3_api->result_text
#define sqlite3_result_text16          sqlite3_api->result_text16
#define sqlite3_result_text16be        sqlite3_api->result_text16be
#define sqlite3_result_text16le        sqlite3_api->result_text16le
#define sqlite3_result_value           sqlite3_api->result_value
#define sqlite3_rollback_hook          sqlite3_api->rollback_hook
#define sqlite3_set_authorizer         sqlite3_api->set_authorizer
#define sqlite3_set_auxdata            sqlite3_api->set_auxdata
#define sqlite3_snprintf               sqlite3_api->xsnprintf
#define sqlite3_step                   sqlite3_api->step
#define sqlite3_table_column_metadata  sqlite3_api->table_column_metadata
#define sqlite3_thread_cleanup         sqlite3_api->thread_cleanup
#define sqlite3_total_changes          sqlite3_api->total_changes
#define sqlite3_trace                  sqlite3_api->trace
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_transfer_bindings      sqlite3_api->transfer_bindings
#endif
#define sqlite3_update_hook            sqlite3_api->update_hook
#define sqlite3_user_data              sqlite3_api->user_data
#define sqlite3_value_blob             sqlite3_api->value_blob
#define sqlite3_value_bytes            sqlite3_api->value_bytes
#define sqlite3_value_bytes16          sqlite3_api->value_bytes16
#define sqlite3_value_double           sqlite3_api->value_double
#define sqlite3_value_int              sqlite3_api->value_int
#define sqlite3_value_int64            sqlite3_api->value_int64
#define sqlite3_value_numeric_type     sqlite3_api->value_numeric_type
#define sqlite3_value_text             sqlite3_api->value_text
#define sqlite3_value_text16           sqlite3_api->value_text16
#define sqlite3_value_text16be         sqlite3_api->value_text16be
#define sqlite3_value_text16le         sqlite3_api->value_text16le
#define sqlite3_value_type             sqlite3_api->value_type
#define sqlite3_vmprintf               sqlite3_api->vmprintf
#define sqlite3_vsnprintf              sqlite3_api->xvsnprintf
#define sqlite3_overload_function      sqlite3_api->overload_function
#define sqlite3_prepare_v2             sqlite3_api->prepare_v2
#define sqlite3_prepare16_v2           sqlite3_api->prepare16_v2
#define sqlite3_clear_bindings         sqlite3_api->clear_bindings
#define sqlite3_bind_zeroblob          sqlite3_api->bind_zeroblob
#define sqlite3_blob_bytes             sqlite3_api->blob_bytes
#define sqlite3_blob_close             sqlite3_api->blob_close
#define sqlite3_blob_open              sqlite3_api->blob_open
#define sqlite3_blob_read              sqlite3_api->blob_read
#define sqlite3_blob_write             sqlite3_api->blob_write
#define sqlite3_create_collation_v2    sqlite3_api->create_collation_v2
#define sqlite3_file_control           sqlite3_api->file_control
#define sqlite3_memory_highwater       sqlite3_api->memory_highwater
#define sqlite3_memory_used            sqlite3_api->memory_used
#define sqlite3_mutex_alloc            sqlite3_api->mutex_alloc
#define sqlite3_mutex_enter            sqlite3_api->mutex_enter
#define sqlite3_mutex_free             sqlite3_api->mutex_free
#define sqlite3_mutex_leave            sqlite3_api->mutex_leave
#define sqlite3_mutex_try              sqlite3_api->mutex_try
#define sqlite3_open_v2                sqlite3_api->open_v2
#define sqlite3_release_memory         sqlite3_api->release_memory
#define sqlite3_result_error_nomem     sqlite3_api->result_error_nomem
#define sqlite3_result_error_toobig    sqlite3_api->result_error_toobig
#define sqlite3_sleep                  sqlite3_api->sleep
#define sqlite3_soft_heap_limit        sqlite3_api->soft_heap_limit
#define sqlite3_vfs_find               sqlite3_api->vfs_find
#define sqlite3_vfs_register           sqlite3_api->vfs_register
#define sqlite3_vfs_unregister         sqlite3_api->vfs_unregister
#define sqlite3_threadsafe             sqlite3_api->xthreadsafe
#define sqlite3_result_zeroblob        sqlite3_api->result_zeroblob
#define sqlite3_result_error_code      sqlite3_api->result_error_code
#define sqlite3_test_control           sqlite3_api->test_control
#define sqlite3_randomness             sqlite3_api->randomness
#define sqlite3_context_db_handle      sqlite3_api->context_db_handle
#define sqlite3_extended_result_codes  sqlite3_api->extended_result_codes
#define sqlite3_limit                  sqlite3_api->limit
#define sqlite3_next_stmt              sqlite3_api->next_stmt
#define sqlite3_sql                    sqlite3_api->sql
#define sqlite3_status                 sqlite3_api->status
#define sqlite3_backup_finish          sqlite3_api->backup_finish
#define sqlite3_backup_init            sqlite3_api->backup_init
#define sqlite3_backup_pagecount       sqlite3_api->backup_pagecount
#define sqlite3_backup_remaining       sqlite3_api->backup_remaining
#define sqlite3_backup_step            sqlite3_api->backup_step
#define sqlite3_compileoption_get      sqlite3_api->compileoption_get
#define sqlite3_compileoption_used     sqlite3_api->compileoption_used
#define sqlite3_create_function_v2     sqlite3_api->create_function_v2
#define sqlite3_db_config              sqlite3_api->db_config
#define sqlite3_db_mutex               sqlite3_api->db_mutex
#define sqlite3_db_status              sqlite3_api->db_status
#define sqlite3_extended_errcode       sqlite3_api->extended_errcode
#define sqlite3_log                    sqlite3_api->log
#define sqlite3_soft_heap_limit64      sqlite3_api->soft_heap_limit64
#define sqlite3_sourceid               sqlite3_api->sourceid
#define sqlite3_stmt_status            sqlite3_api->stmt_status
#define sqlite3_strnicmp               sqlite3_api->strnicmp
#define sqlite3_unlock_notify          sqlite3_api->unlock_notify
#define sqlite3_wal_autocheckpoint     sqlite3_api->wal_autocheckpoint
#define sqlite3_wal_checkpoint         sqlite3_api->wal_checkpoint
#define sqlite3_wal_hook               sqlite3_api->wal_hook
#define sqlite3_blob_reopen            sqlite3_api->blob_reopen
#define sqlite3_vtab_config            sqlite3_api->vtab_config
#define sqlite3_vtab_on_conflict       sqlite3_api->vtab_on_conflict
/* Version 3.7.16 and later */
#define sqlite3_close_v2               sqlite3_api->close_v2
#define sqlite3_db_filename            sqlite3_api->db_filename
#define sqlite3_db_readonly            sqlite3_api->db_readonly
#define sqlite3_db_release_memory      sqlite3_api->db_release_memory
#define sqlite3_errstr                 sqlite3_api->errstr
#define sqlite3_stmt_busy              sqlite3_api->stmt_busy
#define sqlite3_stmt_readonly          sqlite3_api->stmt_readonly
#define sqlite3_stricmp                sqlite3_api->stricmp
#define sqlite3_uri_boolean            sqlite3_api->uri_boolean
#define sqlite3_uri_int64              sqlite3_api->uri_int64
#define sqlite3_uri_parameter          sqlite3_api->uri_parameter
#define sqlite3_uri_vsnprintf          sqlite3_api->xvsnprintf
#define sqlite3_wal_checkpoint_v2      sqlite3_api->wal_checkpoint_v2
/* Version 3.8.7 and later */
#define sqlite3_auto_extension         sqlite3_api->auto_extension
#define sqlite3_bind_blob64            sqlite3_api->bind_blob64
#define sqlite3_bind_text64            sqlite3_api->bind_text64
#define sqlite3_cancel_auto_extension  sqlite3_api->cancel_auto_extension
#define sqlite3_load_extension         sqlite3_api->load_extension
#define sqlite3_malloc64               sqlite3_api->malloc64
#define sqlite3_msize                  sqlite3_api->msize
#define sqlite3_realloc64              sqlite3_api->realloc64
#define sqlite3_reset_auto_extension   sqlite3_api->reset_auto_extension
#define sqlite3_result_blob64          sqlite3_api->result_blob64
#define sqlite3_result_text64          sqlite3_api->result_text64
#define sqlite3_strglob                sqlite3_api->strglob
/* Version 3.8.11 and later */
#define sqlite3_value_dup              sqlite3_api->value_dup
#define sqlite3_value_free             sqlite3_api->value_free
#define sqlite3_result_zeroblob64      sqlite3_api->result_zeroblob64
#define sqlite3_bind_zeroblob64        sqlite3_api->bind_zeroblob64
/* Version 3.9.0 and later */
#define sqlite3_value_subtype          sqlite3_api->value_subtype
#define sqlite3_result_subtype         sqlite3_api->result_subtype
/* Version 3.10.0 and later */
#define sqlite3_status64               sqlite3_api->status64
#define sqlite3_strlike                sqlite3_api->strlike
#define sqlite3_db_cacheflush          sqlite3_api->db_cacheflush
/* Version 3.12.0 and later */
#define sqlite3_system_errno           sqlite3_api->system_errno
/* Version 3.14.0 and later */
#define sqlite3_trace_v2               sqlite3_api->trace_v2
#define sqlite3_expanded_sql           sqlite3_api->expanded_sql
/* Version 3.18.0 and later */
#define sqlite3_set_last_insert_rowid  sqlite3_api->set_last_insert_rowid
/* Version 3.20.0 and later */
#define sqlite3_prepare_v3             sqlite3_api->prepare_v3
#define sqlite3_prepare16_v3           sqlite3_api->prepare16_v3
#define sqlite3_bind_pointer           sqlite3_api->bind_pointer
#define sqlite3_result_pointer         sqlite3_api->result_pointer
#define sqlite3_value_pointer          sqlite3_api->value_pointer
/* Version 3.22.0 and later */
#define sqlite3_vtab_nochange          sqlite3_api->vtab_nochange
#define sqlite3_value_nochange         sqlite3_api->value_nochange
#define sqlite3_vtab_collation         sqlite3_api->vtab_collation
/* Version 3.24.0 and later */
#define sqlite3_keyword_count          sqlite3_api->keyword_count
#define sqlite3_keyword_name           sqlite3_api->keyword_name
#define sqlite3_keyword_check          sqlite3_api->keyword_check
#define sqlite3_str_new                sqlite3_api->str_new
#define sqlite3_str_finish             sqlite3_api->str_finish
#define sqlite3_str_appendf            sqlite3_api->str_appendf
#define sqlite3_str_vappendf           sqlite3_api->str_vappendf
#define sqlite3_str_append             sqlite3_api->str_append
#define sqlite3_str_appendall          sqlite3_api->str_appendall
#define sqlite3_str_appendchar         sqlite3_api->str_appendchar
#define sqlite3_str_reset              sqlite3_api->str_reset
#define sqlite3_str_errcode            sqlite3_api->str_errcode
#define sqlite3_str_length             sqlite3_api->str_length
#define sqlite3_str_value              sqlite3_api->str_value
/* Version 3.25.0 and later */
#define sqlite3_create_window_function sqlite3_api->create_window_function
/* Version 3.26.0 and later */
#define sqlite3_normalized_sql         sqlite3_api->normalized_sql
/* Version 3.28.0 and later */
#define sqlite3_stmt_isexplain         sqlite3_api->stmt_isexplain
#define sqlite3_value_frombind         sqlite3_api->value_frombind
/* Version 3.30.0 and later */
#define sqlite3_drop_modules           sqlite3_api->drop_modules
/* Version 3.31.0 and later */
#define sqlite3_hard_heap_limit64      sqlite3_api->hard_heap_limit64
#define sqlite3_uri_key                sqlite3_api->uri_key
#define sqlite3_filename_database      sqlite3_api->filename_database
#define sqlite3_filename_journal       sqlite3_api->filename_journal
#define sqlite3_filename_wal           sqlite3_api->filename_wal
/* Version 3.32.0 and later */
#define sqlite3_create_filename        sqlite3_api->create_filename
#define sqlite3_free_filename          sqlite3_api->free_filename
#define sqlite3_database_file_object   sqlite3_api->database_file_object
/* Version 3.34.0 and later */
#define sqlite3_txn_state              sqlite3_api->txn_state
/* Version 3.36.1 and later */
#define sqlite3_changes64              sqlite3_api->changes64
#define sqlite3_total_changes64        sqlite3_api->total_changes64
/* Version 3.37.0 and later */
#define sqlite3_autovacuum_pages       sqlite3_api->autovacuum_pages
/* Version 3.38.0 and later */
#define sqlite3_error_offset           sqlite3_api->error_offset
#define sqlite3_vtab_rhs_value         sqlite3_api->vtab_rhs_value
#define sqlite3_vtab_distinct          sqlite3_api->vtab_distinct
#define sqlite3_vtab_in                sqlite3_api->vtab_in
#define sqlite3_vtab_in_first          sqlite3_api->vtab_in_first
#define sqlite3_vtab_in_next           sqlite3_api->vtab_in_next
/* Version 3.39.0 and later */
#ifndef SQLITE_OMIT_DESERIALIZE
#define sqlite3_deserialize            sqlite3_api->deserialize
#define sqlite3_serialize              sqlite3_api->serialize
#endif
#define sqlite3_db_name                sqlite3_api->db_name
/* Version 3.40.0 and later */
#define sqlite3_value_encoding         sqlite3_api->value_encoding
/* Version 3.41.0 and later */
#define sqlite3_is_interrupted         sqlite3_api->is_interrupted
/* Version 3.43.0 and later */
#define sqlite3_stmt_explain           sqlite3_api->stmt_explain
/* Version 3.44.0 and later */
#define sqlite3_get_clientdata         sqlite3_api->get_clientdata
#define sqlite3_set_clientdata         sqlite3_api->set_clientdata
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
  /* This case when the file really is being compiled as a loadable
  ** extension */
# define SQLITE_EXTENSION_INIT1     const sqlite3_api_routines *sqlite3_api=0;
# define SQLITE_EXTENSION_INIT2(v)  sqlite3_api=v;
# define SQLITE_EXTENSION_INIT3     \
    extern const sqlite3_api_routines *sqlite3_api;
#else
  /* This case when the file is being statically linked into the
  ** application */
# define SQLITE_EXTENSION_INIT1     /*no-op*/
# define SQLITE_EXTENSION_INIT2(v)  (void)v; /* unused parameter */
# define SQLITE_EXTENSION_INIT3     /*no-op*/
#endif
#endif /* SQLITE3EXT_H */