| | |
| | | |
| | | return strOut; |
| | | } |
| | | |
| | | // ========== SV数据管理接口实现 ========== |
| | | |
| | | void CGlass::addSVData(int machineId, const std::string& dataType, const SVDataItem& dataItem) { |
| | | m_svDatas[machineId][dataType].push_back(dataItem); |
| | | } |
| | | |
| | | void CGlass::addSVData(int machineId, const std::string& dataType, double value) { |
| | | auto now = std::chrono::system_clock::now(); |
| | | m_svDatas[machineId][dataType].emplace_back(now, value); |
| | | } |
| | | |
| | | void CGlass::addSVData(int machineId, const std::string& dataType, int64_t timestamp, double value) { |
| | | // 将int64_t时间戳转换为system_clock::time_point |
| | | std::chrono::system_clock::time_point timePoint{ |
| | | std::chrono::milliseconds(timestamp) // 假设timestamp是毫秒 |
| | | // 如果是秒,使用:std::chrono::seconds(timestamp) |
| | | }; |
| | | m_svDatas[machineId][dataType].emplace_back(timePoint, value); |
| | | } |
| | | |
| | | void CGlass::addSVData(int machineId, const std::string& dataType, const std::vector<SVDataItem>& dataItems) { |
| | | auto& dataList = m_svDatas[machineId][dataType]; |
| | | dataList.insert(dataList.end(), dataItems.begin(), dataItems.end()); |
| | | } |
| | | |
| | | std::vector<SVDataItem> CGlass::getSVData(int machineId, const std::string& dataType) const { |
| | | auto machineIt = m_svDatas.find(machineId); |
| | | if (machineIt != m_svDatas.end()) { |
| | | auto dataIt = machineIt->second.find(dataType); |
| | | if (dataIt != machineIt->second.end()) { |
| | | return dataIt->second; |
| | | } |
| | | } |
| | | return std::vector<SVDataItem>(); |
| | | } |
| | | |
| | | std::vector<std::string> CGlass::getSVDataTypes(int machineId) const { |
| | | std::vector<std::string> types; |
| | | auto machineIt = m_svDatas.find(machineId); |
| | | if (machineIt != m_svDatas.end()) { |
| | | for (const auto& pair : machineIt->second) { |
| | | types.push_back(pair.first); |
| | | } |
| | | } |
| | | return types; |
| | | } |
| | | |
| | | std::unordered_map<std::string, std::vector<SVDataItem>> CGlass::getMachineSVData(int machineId) const { |
| | | auto it = m_svDatas.find(machineId); |
| | | if (it != m_svDatas.end()) { |
| | | return it->second; |
| | | } |
| | | return std::unordered_map<std::string, std::vector<SVDataItem>>(); |
| | | } |
| | | |
| | | const std::unordered_map<int, std::unordered_map<std::string, std::vector<SVDataItem>>>& CGlass::getAllSVData() const { |
| | | return m_svDatas; |
| | | } |
| | | |
| | | bool CGlass::hasSVData(int machineId, const std::string& dataType) const { |
| | | auto machineIt = m_svDatas.find(machineId); |
| | | if (machineIt != m_svDatas.end()) { |
| | | return machineIt->second.find(dataType) != machineIt->second.end(); |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | bool CGlass::hasMachineSVData(int machineId) const { |
| | | return m_svDatas.find(machineId) != m_svDatas.end(); |
| | | } |
| | | |
| | | std::vector<int> CGlass::getMachineIdsWithSVData() const { |
| | | std::vector<int> machineIds; |
| | | for (const auto& pair : m_svDatas) { |
| | | machineIds.push_back(pair.first); |
| | | } |
| | | return machineIds; |
| | | } |
| | | |
| | | void CGlass::clearSVData(int machineId, const std::string& dataType) { |
| | | auto machineIt = m_svDatas.find(machineId); |
| | | if (machineIt != m_svDatas.end()) { |
| | | machineIt->second.erase(dataType); |
| | | // 如果该机器没有其他数据了,也清除机器条目 |
| | | if (machineIt->second.empty()) { |
| | | m_svDatas.erase(machineIt); |
| | | } |
| | | } |
| | | } |
| | | |
| | | void CGlass::clearMachineSVData(int machineId) { |
| | | m_svDatas.erase(machineId); |
| | | } |
| | | |
| | | void CGlass::clearAllSVData() { |
| | | m_svDatas.clear(); |
| | | } |
| | | |
| | | size_t CGlass::getSVDataCount(int machineId, const std::string& dataType) const { |
| | | auto machineIt = m_svDatas.find(machineId); |
| | | if (machineIt != m_svDatas.end()) { |
| | | auto dataIt = machineIt->second.find(dataType); |
| | | if (dataIt != machineIt->second.end()) { |
| | | return dataIt->second.size(); |
| | | } |
| | | } |
| | | return 0; |
| | | } |
| | | |
| | | size_t CGlass::getMachineSVDataCount(int machineId) const { |
| | | size_t total = 0; |
| | | auto machineIt = m_svDatas.find(machineId); |
| | | if (machineIt != m_svDatas.end()) { |
| | | for (const auto& pair : machineIt->second) { |
| | | total += pair.second.size(); |
| | | } |
| | | } |
| | | return total; |
| | | } |
| | | |
| | | size_t CGlass::getTotalSVDataCount() const { |
| | | size_t total = 0; |
| | | for (const auto& machinePair : m_svDatas) { |
| | | for (const auto& dataPair : machinePair.second) { |
| | | total += dataPair.second.size(); |
| | | } |
| | | } |
| | | return total; |
| | | } |
| | | |
| | | std::vector<std::pair<int, SVDataItem>> CGlass::findSVDataByType(const std::string& dataType) const { |
| | | std::vector<std::pair<int, SVDataItem>> result; |
| | | for (const auto& machinePair : m_svDatas) { |
| | | auto dataIt = machinePair.second.find(dataType); |
| | | if (dataIt != machinePair.second.end()) { |
| | | for (const auto& item : dataIt->second) { |
| | | result.emplace_back(machinePair.first, item); |
| | | } |
| | | } |
| | | } |
| | | return result; |
| | | } |
| | | } |
| | |
| | | |
| | | |
| | | namespace SERVO { |
| | | /// 数据项:时间戳 + 数值 |
| | | struct SVDataItem { |
| | | std::chrono::system_clock::time_point timestamp; |
| | | double value; // 或者根据实际情况使用其他类型 |
| | | |
| | | SVDataItem(std::chrono::system_clock::time_point ts, double val) |
| | | : timestamp(ts), value(val) {} |
| | | }; |
| | | |
| | | /// PJ 生命周期(贴近 E40 常见状态) |
| | | enum class GlsState : uint8_t { |
| | | NoState = 0, |
| | |
| | | void addParams(std::vector<CParam>& params); |
| | | std::vector<CParam>& getParams(); |
| | | |
| | | // ========== SV数据管理接口(新设计)========== |
| | | |
| | | // 添加数据到指定机器的指定数据类型 |
| | | void addSVData(int machineId, const std::string& dataType, const SVDataItem& dataItem); |
| | | void addSVData(int machineId, const std::string& dataType, double value); // 自动使用当前时间 |
| | | void addSVData(int machineId, const std::string& dataType, int64_t timestamp, double value); |
| | | |
| | | // 批量添加数据到指定机器的指定数据类型 |
| | | void addSVData(int machineId, const std::string& dataType, const std::vector<SVDataItem>& dataItems); |
| | | |
| | | // 获取指定机器的指定数据类型的所有数据 |
| | | std::vector<SVDataItem> getSVData(int machineId, const std::string& dataType) const; |
| | | |
| | | // 获取指定机器的所有数据类型 |
| | | std::vector<std::string> getSVDataTypes(int machineId) const; |
| | | |
| | | // 获取指定机器的所有数据(按数据类型组织) |
| | | std::unordered_map<std::string, std::vector<SVDataItem>> getMachineSVData(int machineId) const; |
| | | |
| | | // 获取所有机器的数据 |
| | | const std::unordered_map<int, std::unordered_map<std::string, std::vector<SVDataItem>>>& getAllSVData() const; |
| | | |
| | | // 检查指定机器是否有指定类型的数据 |
| | | bool hasSVData(int machineId, const std::string& dataType) const; |
| | | |
| | | // 检查指定机器是否有任何数据 |
| | | bool hasMachineSVData(int machineId) const; |
| | | |
| | | // 获取所有有SV数据的机器ID |
| | | std::vector<int> getMachineIdsWithSVData() const; |
| | | |
| | | // 清空指定机器的指定数据类型的数据 |
| | | void clearSVData(int machineId, const std::string& dataType); |
| | | |
| | | // 清空指定机器的所有数据 |
| | | void clearMachineSVData(int machineId); |
| | | |
| | | // 清空所有机器的所有数据 |
| | | void clearAllSVData(); |
| | | |
| | | // 获取指定机器的指定数据类型的数据数量 |
| | | size_t getSVDataCount(int machineId, const std::string& dataType) const; |
| | | |
| | | // 获取指定机器的总数据数量 |
| | | size_t getMachineSVDataCount(int machineId) const; |
| | | |
| | | // 获取所有机器的总数据数量 |
| | | size_t getTotalSVDataCount() const; |
| | | |
| | | // 获取指定数据类型在所有机器中的数据 |
| | | std::vector<std::pair<int, SVDataItem>> findSVDataByType(const std::string& dataType) const; |
| | | |
| | | private: |
| | | MaterialsType m_type; |
| | | std::string m_strID; |
| | |
| | | BOOL m_bScheduledForProcessing; /* 是否将加工处理 */ |
| | | CProcessJob* m_pProcessJob; |
| | | std::vector<CParam> m_params; // 工艺参数 |
| | | |
| | | // 新的三层数据结构:机器ID -> 数据类型 -> 数据列表 |
| | | std::unordered_map<int, std::unordered_map<std::string, std::vector<SVDataItem>>> m_svDatas; |
| | | }; |
| | | } |
| | | |
| | |
| | | |
| | | |
| | | namespace SERVO { |
| | | static std::unordered_map<int, std::vector<std::string>> MACHINE_DATA_TYPES = { |
| | | {EQ_ID_Bonder1, { |
| | | "气囊压力", "上腔压力", "管道真空规值", "腔体真空规值", |
| | | "上腔温度1", "上腔温度2", "上腔温度3", "上腔温度4", |
| | | "上腔温度5", "上腔温度6", "下腔温度1", "下腔温度2", |
| | | "下腔温度3", "下腔温度4", "下腔温度5", "下腔温度6" |
| | | }}, |
| | | {EQ_ID_Bonder2, { |
| | | "气囊压力", "上腔压力", "管道真空规值", "腔体真空规值", |
| | | "上腔温度1", "上腔温度2", "上腔温度3", "上腔温度4", |
| | | "上腔温度5", "上腔温度6", "下腔温度1", "下腔温度2", |
| | | "下腔温度3", "下腔温度4", "下腔温度5", "下腔温度6" |
| | | }}, |
| | | {EQ_ID_VACUUMBAKE, { |
| | | "A腔真空规值", "A腔温控1", "A腔温控2", "A腔温控4", |
| | | "A腔温控5", "A腔温控6", "A腔温控7", "B腔真空规值", |
| | | "B腔温控1", "B腔温控2", "B腔温控4", "B腔温控5", |
| | | "B腔温控6", "B腔温控7" |
| | | }}, |
| | | {EQ_ID_BAKE_COOLING, { |
| | | "A烘烤温控1", "A烘烤温控2", "A烘烤温控4", "A烘烤温控5", |
| | | "A烘烤温控6", "A烘烤温控7", "B烘烤温控1", "B烘烤温控2", |
| | | "B烘烤温控4", "B烘烤温控5", "B烘烤温控6", "B烘烤温控7" |
| | | }} |
| | | }; |
| | | |
| | | static inline int64_t now_ms_epoch() { |
| | | using namespace std::chrono; |
| | | return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count(); |
| | |
| | | |
| | | |
| | | // 以下加入到曲线数据中 |
| | | |
| | | |
| | | const int64_t ts = now_ms_epoch(); |
| | | int eqid = ((CEquipment*)pEquipment)->getID(); |
| | | if (eqid == EQ_ID_Bonder1 || eqid == EQ_ID_Bonder2) { |
| | | m_pCollector->buffersPush(eqid, 1, ts, params.at(1).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 2, ts, params.at(2).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 3, ts, params.at(3).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 4, ts, params.at(4).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 5, ts, params.at(5).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 6, ts, params.at(6).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 7, ts, params.at(7).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 8, ts, params.at(8).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 9, ts, params.at(9).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 10, ts, params.at(10).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 11, ts, params.at(11).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 12, ts, params.at(12).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 13, ts, params.at(13).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 14, ts, params.at(14).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 15, ts, params.at(15).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 16, ts, params.at(16).getDoubleValue()); |
| | | // 定义 Bonder 的特定映射 |
| | | std::vector<std::pair<int, int>> bonderMapping = { |
| | | {1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}, {7, 7}, |
| | | {8, 8}, {9, 9}, {10, 10}, {11, 11}, {12, 12}, {13, 13}, {14, 14}, {15, 15}, {16, 16} |
| | | }; |
| | | |
| | | CGlass* pGlass = ((CEquipment*)pEquipment)->getGlassFromSlot(0); |
| | | auto& bonderTypes = MACHINE_DATA_TYPES[eqid]; |
| | | for (const auto& mapping : bonderMapping) { |
| | | int paramIndex = mapping.first; |
| | | int channel = mapping.second; |
| | | |
| | | if (paramIndex < params.size() && channel - 1 < bonderTypes.size()) { |
| | | if(m_pCollector != nullptr) |
| | | m_pCollector->buffersPush(eqid, channel, ts, params.at(paramIndex).getDoubleValue()); |
| | | if(pGlass != nullptr) |
| | | pGlass->addSVData(eqid, bonderTypes[channel], ts, params.at(paramIndex).getDoubleValue()); |
| | | } |
| | | } |
| | | } |
| | | else if (eqid == EQ_ID_VACUUMBAKE) { |
| | | m_pCollector->buffersPush(eqid, 1, ts, params.at(1).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 2, ts, params.at(2).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 3, ts, params.at(3).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 4, ts, params.at(4).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 5, ts, params.at(5).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 6, ts, params.at(6).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 7, ts, params.at(7).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 8, ts, params.at(10).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 9, ts, params.at(11).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 10, ts, params.at(12).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 11, ts, params.at(13).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 12, ts, params.at(14).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 13, ts, params.at(15).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 14, ts, params.at(16).getDoubleValue()); |
| | | // 定义 VACUUMBAKE 的特定映射 |
| | | std::vector<std::pair<int, int>> vacuumMapping = { |
| | | {1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}, {7, 7}, |
| | | {10, 8}, {11, 9}, {12, 10}, {13, 11}, {14, 12}, {15, 13}, {16, 14} |
| | | }; |
| | | |
| | | CGlass* pGlass = ((CEquipment*)pEquipment)->getGlassFromSlot(0); |
| | | auto& bonderTypes = MACHINE_DATA_TYPES[eqid]; |
| | | for (const auto& mapping : vacuumMapping) { |
| | | int paramIndex = mapping.first; |
| | | int channel = mapping.second; |
| | | |
| | | if (paramIndex < params.size() && channel - 1 < bonderTypes.size()) { |
| | | if (m_pCollector != nullptr) |
| | | m_pCollector->buffersPush(eqid, channel, ts, params.at(paramIndex).getDoubleValue()); |
| | | if (pGlass != nullptr) |
| | | pGlass->addSVData(eqid, bonderTypes[channel], ts, params.at(paramIndex).getDoubleValue()); |
| | | } |
| | | } |
| | | } |
| | | else if (eqid == EQ_ID_BAKE_COOLING) { |
| | | m_pCollector->buffersPush(eqid, 1, ts, params.at(1).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 2, ts, params.at(2).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 3, ts, params.at(3).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 4, ts, params.at(4).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 5, ts, params.at(5).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 6, ts, params.at(6).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 7, ts, params.at(11).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 8, ts, params.at(12).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 9, ts, params.at(13).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 10, ts, params.at(14).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 11, ts, params.at(15).getDoubleValue()); |
| | | m_pCollector->buffersPush(eqid, 12, ts, params.at(16).getDoubleValue()); |
| | | // 定义 BAKE_COOLING 的特定映射 |
| | | std::vector<std::pair<int, int>> coolingMapping = { |
| | | {1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}, |
| | | {11, 7}, {12, 8}, {13, 9}, {14, 10}, {15, 11}, {16, 12} |
| | | }; |
| | | |
| | | CGlass* pGlass = ((CEquipment*)pEquipment)->getGlassFromSlot(0); |
| | | auto& coolingTypes = MACHINE_DATA_TYPES[eqid]; |
| | | for (const auto& mapping : coolingMapping) { |
| | | int paramIndex = mapping.first; |
| | | int channel = mapping.second; |
| | | |
| | | if (paramIndex < params.size() && channel - 1 < coolingTypes.size()) { |
| | | if (m_pCollector != nullptr) |
| | | m_pCollector->buffersPush(eqid, channel, ts, params.at(paramIndex).getDoubleValue()); |
| | | if (pGlass != nullptr) |
| | | pGlass->addSVData(eqid, coolingTypes[channel], ts, params.at(paramIndex).getDoubleValue()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | |
| | | |
| | | |
| | | // 2) 为通道设置“曲线名称” |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 1, "气囊压力"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 2, "上腔压力"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 3, "管道真空规值"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 4, "腔体真空规值"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 5, "上腔温度1"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 6, "上腔温度2"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 7, "上腔温度3"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 8, "上腔温度4"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 9, "上腔温度5"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 10, "上腔温度6"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 11, "下腔温度1"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 12, "下腔温度2"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 13, "下腔温度3"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 14, "下腔温度4"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 15, "下腔温度5"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 16, "下腔温度6"); |
| | | auto& bonderTypes = MACHINE_DATA_TYPES[EQ_ID_Bonder1]; |
| | | for (size_t i = 0; i < bonderTypes.size(); ++i) { |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, i + 1, bonderTypes[i].c_str()); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, i + 1, bonderTypes[i].c_str()); |
| | | } |
| | | |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 1, "气囊压力"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 2, "上腔压力"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 3, "管道真空规值"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 4, "腔体真空规值"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 5, "上腔温度1"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 6, "上腔温度2"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 7, "上腔温度3"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 8, "上腔温度4"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 9, "上腔温度5"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 10, "上腔温度6"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 11, "下腔温度1"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 12, "下腔温度2"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 13, "下腔温度3"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 14, "下腔温度4"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 15, "下腔温度5"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 16, "下腔温度6"); |
| | | auto& vacuumbakeTypes = MACHINE_DATA_TYPES[EQ_ID_VACUUMBAKE]; |
| | | for (size_t i = 0; i < bonderTypes.size(); ++i) { |
| | | m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, i + 1, vacuumbakeTypes[i].c_str()); |
| | | } |
| | | |
| | | m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 1, "A腔真空规值"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 2, "A腔温控1"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 3, "A腔温控2"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 4, "A腔温控4"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 5, "A腔温控5"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 6, "A腔温控6"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 7, "A腔温控7"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 8, "B腔真空规值"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 9, "B腔温控1"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 10, "B腔温控2"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 11, "B腔温控4"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 12, "B腔温控5"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 13, "B腔温控6"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 14, "B腔温控7"); |
| | | |
| | | m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 1, "A烘烤温控1"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 2, "A烘烤温控2"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 3, "A烘烤温控4"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 4, "A烘烤温控5"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 5, "A烘烤温控6"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 6, "A烘烤温控7"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 7, "B烘烤温控1"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 8, "B烘烤温控2"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 9, "B烘烤温控4"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 10, "B烘烤温控5"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 11, "B烘烤温控6"); |
| | | m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 12, "B烘烤温控7"); |
| | | auto& coolingTypes = MACHINE_DATA_TYPES[EQ_ID_BAKE_COOLING]; |
| | | for (size_t i = 0; i < bonderTypes.size(); ++i) { |
| | | m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, i + 1, coolingTypes[i].c_str()); |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | return true; |
| | | } |
| | | |
| | | // 辅助函数:将 ANSI CString 写入文件为 UTF-8 编码 |
| | | bool CPageGlassList::WriteAnsiStringAsUtf8ToFile(const CString& ansiContent, const CString& filePath) |
| | | { |
| | | CFile file; |
| | | if (!file.Open(filePath, CFile::modeCreate | CFile::modeWrite)) { |
| | | return false; |
| | | } |
| | | |
| | | // 写入 UTF-8 BOM |
| | | const unsigned char bom[] = { 0xEF, 0xBB, 0xBF }; |
| | | file.Write(bom, 3); |
| | | |
| | | // 将 ANSI 转换为 Unicode |
| | | int unicodeLength = MultiByteToWideChar(CP_ACP, 0, |
| | | ansiContent, ansiContent.GetLength(), |
| | | NULL, 0); |
| | | |
| | | if (unicodeLength <= 0) { |
| | | file.Close(); |
| | | return false; |
| | | } |
| | | |
| | | wchar_t* unicodeBuffer = new wchar_t[unicodeLength + 1]; |
| | | MultiByteToWideChar(CP_ACP, 0, |
| | | ansiContent, ansiContent.GetLength(), |
| | | unicodeBuffer, unicodeLength); |
| | | unicodeBuffer[unicodeLength] = 0; |
| | | |
| | | // 将 Unicode 转换为 UTF-8 |
| | | int utf8Length = WideCharToMultiByte(CP_UTF8, 0, |
| | | unicodeBuffer, unicodeLength, |
| | | NULL, 0, NULL, NULL); |
| | | |
| | | bool success = false; |
| | | if (utf8Length > 0) { |
| | | char* utf8Buffer = new char[utf8Length]; |
| | | WideCharToMultiByte(CP_UTF8, 0, |
| | | unicodeBuffer, unicodeLength, |
| | | utf8Buffer, utf8Length, NULL, NULL); |
| | | |
| | | file.Write(utf8Buffer, utf8Length); |
| | | delete[] utf8Buffer; |
| | | success = true; |
| | | } |
| | | |
| | | delete[] unicodeBuffer; |
| | | file.Close(); |
| | | return success; |
| | | } |
| | | |
| | | // CPageGlassList 对话框 |
| | | |
| | |
| | | ON_BN_CLICKED(IDC_BUTTON_PREV_PAGE, &CPageGlassList::OnBnClickedButtonPrevPage) |
| | | ON_BN_CLICKED(IDC_BUTTON_NEXT_PAGE, &CPageGlassList::OnBnClickedButtonNextPage) |
| | | ON_NOTIFY(ELCN_SHOWFULLTEXT, IDC_LIST_ALARM, &CPageGlassList::OnShowFullText) |
| | | ON_BN_CLICKED(IDC_BUTTON_EXPORT_ROW, &CPageGlassList::OnBnClickedButtonExportRow) |
| | | END_MESSAGE_MAP() |
| | | |
| | | // ===== 私有小工具 ===== |
| | |
| | | } |
| | | } |
| | | |
| | | void CPageGlassList::OnBnClickedButtonExportRow() |
| | | { |
| | | int nSelected = m_listCtrl.GetSelectionMark(); |
| | | if (nSelected == -1) { |
| | | AfxMessageBox(_T("请先选择一行记录!")); |
| | | return; |
| | | } |
| | | |
| | | // 直接从第一列获取 ID |
| | | CString strId = m_listCtrl.GetItemText(nSelected, 1); |
| | | |
| | | if (strId.IsEmpty()) { |
| | | AfxMessageBox(_T("WIP记录暂不支持保存")); |
| | | return; |
| | | } |
| | | |
| | | // 数据库记录 |
| | | long long recordId = _ttoi64(strId); |
| | | |
| | | // 从数据库查询完整记录 |
| | | auto& db = GlassLogDb::Instance(); |
| | | auto row = db.queryById(recordId); |
| | | |
| | | if (!row) { |
| | | AfxMessageBox(_T("查询记录失败")); |
| | | return; |
| | | } |
| | | |
| | | // 使用 Glass ID 构建默认文件名 |
| | | CString strDefaultFileName; |
| | | CString strGlassId = row->classId.c_str(); |
| | | |
| | | // 移除文件名中的非法字符 |
| | | CString strSanitizedGlassId = strGlassId; |
| | | strSanitizedGlassId.Remove('\\'); |
| | | strSanitizedGlassId.Remove('/'); |
| | | strSanitizedGlassId.Remove(':'); |
| | | strSanitizedGlassId.Remove('*'); |
| | | strSanitizedGlassId.Remove('?'); |
| | | strSanitizedGlassId.Remove('"'); |
| | | strSanitizedGlassId.Remove('<'); |
| | | strSanitizedGlassId.Remove('>'); |
| | | strSanitizedGlassId.Remove('|'); |
| | | |
| | | strDefaultFileName.Format(_T("Glass_%s.json"), strSanitizedGlassId); |
| | | |
| | | // 文件保存对话框,设置默认文件名 |
| | | CFileDialog fileDialog(FALSE, _T("json"), strDefaultFileName, |
| | | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, |
| | | _T("JSON Files (*.json)|*.json|CSV Files (*.csv)|*.csv||")); |
| | | |
| | | if (fileDialog.DoModal() != IDOK) return; |
| | | |
| | | CString filePath = fileDialog.GetPathName(); |
| | | CString fileExt = fileDialog.GetFileExt(); |
| | | |
| | | if (fileExt.CompareNoCase(_T("json")) == 0) { |
| | | // 保存为 JSON |
| | | if (!row->pretty.empty()) { |
| | | CFile file; |
| | | if (file.Open(filePath, CFile::modeCreate | CFile::modeWrite)) { |
| | | file.Write(row->pretty.c_str(), row->pretty.length()); |
| | | file.Close(); |
| | | |
| | | CString strSuccess; |
| | | strSuccess.Format(_T("记录已保存为JSON文件:\n%s"), filePath); |
| | | AfxMessageBox(strSuccess); |
| | | } |
| | | else { |
| | | AfxMessageBox(_T("保存文件失败")); |
| | | } |
| | | } |
| | | else { |
| | | AfxMessageBox(_T("该记录没有JSON数据")); |
| | | } |
| | | } |
| | | else { |
| | | // 保存为 CSV 格式 - 分段式 |
| | | CString csvContent; |
| | | |
| | | // === 第一部分:基础信息 === |
| | | csvContent += _T("=== 基础信息 ===\n"); |
| | | csvContent += _T("ID,Cassette序列号,Job序列号,Glass ID,物料类型,状态,开始时间,结束时间,绑定Glass ID,AOI结果,路径\n"); |
| | | |
| | | CString baseInfoRow; |
| | | baseInfoRow.Format(_T("%lld,%d,%d,%s,%d,%d,%s,%s,%s,%d,%s\n"), |
| | | row->id, row->cassetteSeqNo, row->jobSeqNo, |
| | | CString(row->classId.c_str()), row->materialType, row->state, |
| | | CString(row->tStart.c_str()), CString(row->tEnd.c_str()), |
| | | CString(row->buddyId.c_str()), row->aoiResult, |
| | | CString(row->path.c_str())); |
| | | csvContent += baseInfoRow; |
| | | |
| | | // === 第二部分:工艺参数 === |
| | | csvContent += _T("\n=== 工艺参数 ===\n"); |
| | | |
| | | // 如果有 pretty 字段,解析工艺参数 |
| | | if (!row->pretty.empty()) { |
| | | SERVO::CGlass tempGlass; |
| | | if (GlassJson::FromString(row->pretty, tempGlass)) { |
| | | auto& params = tempGlass.getParams(); |
| | | if (!params.empty()) { |
| | | // 工艺参数表头 - 调整后的列 |
| | | csvContent += _T("参数名称,参数ID,数值,机器单元\n"); |
| | | |
| | | // 工艺参数数据 - 调整后的格式 |
| | | for (auto& param : params) { |
| | | CString paramRow; |
| | | CString valueStr; |
| | | |
| | | // 根据参数类型格式化数值 |
| | | if (param.getValueType() == PVT_INT) { |
| | | valueStr.Format(_T("%d"), param.getIntValue()); |
| | | } |
| | | else { |
| | | valueStr.Format(_T("%.3f"), param.getDoubleValue()); |
| | | } |
| | | |
| | | // 调整后的格式:去掉数值类型列 |
| | | paramRow.Format(_T("%s,%s,%s,%s\n"), |
| | | CString(param.getName().c_str()), |
| | | CString(param.getId().c_str()), |
| | | valueStr, |
| | | CString(param.getUnit().c_str())); // 这里显示机器单元 |
| | | |
| | | csvContent += paramRow; |
| | | } |
| | | } |
| | | else { |
| | | csvContent += _T("无工艺参数数据\n"); |
| | | } |
| | | } |
| | | else { |
| | | csvContent += _T("无法解析工艺参数\n"); |
| | | } |
| | | } |
| | | else { |
| | | csvContent += _T("无工艺参数数据\n"); |
| | | } |
| | | |
| | | // 使用辅助函数保存为 UTF-8 编码 |
| | | if (WriteAnsiStringAsUtf8ToFile(csvContent, filePath)) { |
| | | CString strSuccess; |
| | | strSuccess.Format(_T("记录已保存为CSV文件:\n%s"), filePath); |
| | | AfxMessageBox(strSuccess); |
| | | } |
| | | else { |
| | | AfxMessageBox(_T("保存文件失败")); |
| | | } |
| | | } |
| | | } |
| | | |
| | | void CPageGlassList::OnBnClickedButtonPrevPage() |
| | | { |
| | | if (m_nCurPage > 1) { |
| | |
| | | void UpdateWipData(); |
| | | bool eraseGlassInVector(SERVO::CGlass* pGlass, std::vector<SERVO::CGlass*>& glasses); |
| | | void UpdateWipRow(unsigned int index, SERVO::CGlass* pGlass); |
| | | bool WriteAnsiStringAsUtf8ToFile(const CString& ansiContent, const CString& filePath); |
| | | |
| | | // 对话框数据 |
| | | #ifdef AFX_DESIGN_TIME |
| | |
| | | afx_msg void OnShowFullText(NMHDR* pNMHDR, LRESULT* pResult); |
| | | virtual BOOL PreTranslateMessage(MSG* pMsg); |
| | | DECLARE_MESSAGE_MAP() |
| | | public: |
| | | afx_msg void OnBnClickedButtonExportRow(); |
| | | }; |
| | |
| | | root["path"] = std::move(arr); |
| | | } |
| | | |
| | | root["payload_version"] = 1; |
| | | // SV数据:三层结构序列化 |
| | | { |
| | | Json::Value svDataObj(Json::objectValue); |
| | | const auto& allSVData = g.getAllSVData(); |
| | | |
| | | for (const auto& machinePair : allSVData) { |
| | | int machineId = machinePair.first; |
| | | const auto& dataTypes = machinePair.second; |
| | | |
| | | Json::Value machineObj(Json::objectValue); |
| | | for (const auto& dataTypePair : dataTypes) { |
| | | const std::string& dataType = dataTypePair.first; |
| | | const auto& dataItems = dataTypePair.second; |
| | | |
| | | Json::Value dataArray(Json::arrayValue); |
| | | for (const auto& item : dataItems) { |
| | | Json::Value itemObj(Json::objectValue); |
| | | // 时间戳转换为毫秒字符串 |
| | | auto ms = std::chrono::duration_cast<std::chrono::milliseconds>( |
| | | item.timestamp.time_since_epoch()).count(); |
| | | itemObj["t"] = std::to_string(ms); |
| | | itemObj["v"] = item.value; |
| | | dataArray.append(itemObj); |
| | | } |
| | | machineObj[dataType] = dataArray; |
| | | } |
| | | svDataObj[std::to_string(machineId)] = machineObj; |
| | | } |
| | | root["sv_datas"] = svDataObj; |
| | | } |
| | | |
| | | root["payload_version"] = 2; // 版本升级,因为新增了sv_datas字段 |
| | | return root; |
| | | } |
| | | |
| | |
| | | if (JBool(n, "processed", false)) tail->processEnd(); |
| | | } |
| | | } |
| | | |
| | | // SV数据:反序列化三层结构 |
| | | if (root.isMember("sv_datas") && root["sv_datas"].isObject()) { |
| | | g.clearAllSVData(); // 清空现有数据 |
| | | |
| | | const auto& svDataObj = root["sv_datas"]; |
| | | auto memberNames = svDataObj.getMemberNames(); |
| | | |
| | | for (const auto& machineIdStr : memberNames) { |
| | | int machineId = std::stoi(machineIdStr); |
| | | const auto& machineObj = svDataObj[machineIdStr]; |
| | | |
| | | auto dataTypeNames = machineObj.getMemberNames(); |
| | | for (const auto& dataType : dataTypeNames) { |
| | | const auto& dataArray = machineObj[dataType]; |
| | | if (dataArray.isArray()) { |
| | | for (const auto& itemObj : dataArray) { |
| | | long long timestampMs = 0; |
| | | double value = 0.0; |
| | | |
| | | if (get_ll_from_json(itemObj, "t", timestampMs)) { |
| | | value = JDouble(itemObj, "v", 0.0); |
| | | // 添加SV数据 |
| | | g.addSVData(machineId, dataType, timestampMs, value); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // ==================== 便捷封装 ==================== |
| | |
| | | ofs.flush(); |
| | | return rows; |
| | | } |
| | | |
| | | std::optional<GlassLogDb::Row> GlassLogDb::queryById(long long id) { |
| | | std::lock_guard<std::mutex> lk(mtx_); |
| | | |
| | | const char* 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 |
| | | " WHERE id = ?"; |
| | | |
| | | sqlite3_stmt* stmt = nullptr; |
| | | try { |
| | | throwIf(sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr), db_, "prepare queryById"); |
| | | throwIf(sqlite3_bind_int64(stmt, 1, id), db_, "bind id"); |
| | | |
| | | std::optional<Row> result; |
| | | |
| | | int rc = sqlite3_step(stmt); |
| | | if (rc == SQLITE_ROW) { |
| | | Row row; |
| | | row.id = sqlite3_column_int64(stmt, 0); |
| | | row.cassetteSeqNo = sqlite3_column_int(stmt, 1); |
| | | row.jobSeqNo = sqlite3_column_int(stmt, 2); |
| | | row.classId = safe_text(stmt, 3); |
| | | row.materialType = sqlite3_column_int(stmt, 4); |
| | | row.state = sqlite3_column_int(stmt, 5); |
| | | row.tStart = safe_text(stmt, 6); |
| | | row.tEnd = safe_text(stmt, 7); |
| | | row.buddyId = safe_text(stmt, 8); |
| | | row.aoiResult = sqlite3_column_int(stmt, 9); |
| | | row.path = safe_text(stmt, 10); |
| | | row.params = safe_text(stmt, 11); |
| | | row.pretty = safe_text(stmt, 12); |
| | | |
| | | result = std::move(row); |
| | | } |
| | | else if (rc != SQLITE_DONE) { |
| | | throwIf(rc, db_, "queryById step"); |
| | | } |
| | | |
| | | sqlite3_finalize(stmt); |
| | | return result; |
| | | } |
| | | catch (const std::exception& e) { |
| | | if (stmt) sqlite3_finalize(stmt); |
| | | TRACE("GlassLogDb::queryById exception: %s\n", e.what()); |
| | | return std::nullopt; |
| | | } |
| | | } |
| | | |
| | |
| | | int limit = 50, |
| | | int offset = 0); |
| | | |
| | | // 按 ID 查询单条记录(包含 pretty 字段) |
| | | std::optional<Row> queryById(long long id); |
| | | |
| | | // 统计与 filters 相同条件的总数 |
| | | long long count(const Filters& filters = {}); |
| | | |