| | |
| | | #include <regex> |
| | | #include <sstream> |
| | | |
| | | // ---- Encoding helpers ---- |
| | | static bool hasUtf8Bom(const std::string& s) |
| | | { |
| | | return s.size() >= 3 && |
| | | static_cast<unsigned char>(s[0]) == 0xEF && |
| | | static_cast<unsigned char>(s[1]) == 0xBB && |
| | | static_cast<unsigned char>(s[2]) == 0xBF; |
| | | } |
| | | |
| | | static bool isLikelyUtf8(const std::string& s) |
| | | { |
| | | // Simple heuristic: try to convert; if success without errors, treat as UTF-8. |
| | | int wlen = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s.c_str(), (int)s.size(), nullptr, 0); |
| | | return wlen > 0; |
| | | } |
| | | |
| | | static CStringW Utf8ToWide(const char* psz) |
| | | { |
| | | if (psz == nullptr) return L""; |
| | | int wlen = MultiByteToWideChar(CP_UTF8, 0, psz, -1, nullptr, 0); |
| | | if (wlen <= 0) return L""; |
| | | CStringW ws; |
| | | LPWSTR buf = ws.GetBufferSetLength(wlen - 1); |
| | | MultiByteToWideChar(CP_UTF8, 0, psz, -1, buf, wlen); |
| | | ws.ReleaseBuffer(); |
| | | return ws; |
| | | } |
| | | |
| | | static CStringW AnsiToWide(const char* psz) |
| | | { |
| | | if (psz == nullptr) return L""; |
| | | int wlen = MultiByteToWideChar(CP_ACP, 0, psz, -1, nullptr, 0); |
| | | if (wlen <= 0) return L""; |
| | | CStringW ws; |
| | | LPWSTR buf = ws.GetBufferSetLength(wlen - 1); |
| | | MultiByteToWideChar(CP_ACP, 0, psz, -1, buf, wlen); |
| | | ws.ReleaseBuffer(); |
| | | return ws; |
| | | } |
| | | // ---- End helpers ---- |
| | | |
| | | // ControlState values (keep in sync with Model::ControlState / VariableList.txt) |
| | | static constexpr uint8_t kControlStateOnlineRemote = 5; |
| | | |
| | |
| | | return 0; |
| | | } |
| | | |
| | | int CHsmsPassive::loadDataVarialbles(const char* pszFilepath) |
| | | { |
| | | if (pszFilepath == NULL) { |
| | | return -1; |
| | | } |
| | | m_strDataVariableFilepath = pszFilepath; |
| | | m_bDataVariableUtf8 = false; |
| | | m_bDataVariableUtf8Bom = false; |
| | | CFile file; |
| | | if (!file.Open(pszFilepath, CFile::modeRead | CFile::shareDenyNone)) { |
| | | return -1; |
| | | } |
| | | const ULONGLONG nLen = file.GetLength(); |
| | | if (nLen == 0) { |
| | | return -1; |
| | | } |
| | | std::string buffer; |
| | | buffer.resize(static_cast<size_t>(nLen)); |
| | | file.Read(buffer.data(), static_cast<UINT>(nLen)); |
| | | file.Close(); |
| | | |
| | | if (hasUtf8Bom(buffer)) { |
| | | m_bDataVariableUtf8 = true; |
| | | m_bDataVariableUtf8Bom = true; |
| | | buffer = buffer.substr(3); |
| | | } |
| | | else if (isLikelyUtf8(buffer)) { |
| | | m_bDataVariableUtf8 = true; |
| | | } |
| | | CStringW content = m_bDataVariableUtf8 ? Utf8ToWide(buffer.c_str()) : AnsiToWide(buffer.c_str()); |
| | | |
| | | // Regex: DVID,DV Name,DV Format,DV Remark |
| | | std::wregex pattern(L"^\\d+,[^,]*,[^,]*,.*"); |
| | | std::vector<SERVO::CDataVariable*> dataVars; |
| | | int index; |
| | | CStringW strLine, strId, strName, strFormat, strRemark; |
| | | std::wstringstream ss(content.GetString()); |
| | | auto narrowFromW = [](const CStringW& s) -> std::string { |
| | | int need = WideCharToMultiByte(CP_ACP, 0, s, -1, nullptr, 0, nullptr, nullptr); |
| | | if (need <= 0) return {}; |
| | | std::string out(static_cast<size_t>(need - 1), '\0'); |
| | | WideCharToMultiByte(CP_ACP, 0, s, -1, out.data(), need, nullptr, nullptr); |
| | | return out; |
| | | }; |
| | | std::wstring line; |
| | | while (std::getline(ss, line, L'\n')) { |
| | | strLine = line.c_str(); |
| | | strLine.Trim(); |
| | | if (strLine.IsEmpty()) continue; |
| | | if (!std::regex_match(static_cast<LPCWSTR>(strLine), pattern)) { |
| | | continue; |
| | | } |
| | | index = strLine.Find(L",", 0); |
| | | if (index < 0) continue; |
| | | strId = strLine.Left(index); |
| | | |
| | | strLine = strLine.Right(strLine.GetLength() - index - 1); |
| | | index = strLine.Find(L",", 0); |
| | | if (index < 0) continue; |
| | | strName = strLine.Left(index); |
| | | |
| | | strLine = strLine.Right(strLine.GetLength() - index - 1); |
| | | index = strLine.Find(L",", 0); |
| | | if (index < 0) continue; |
| | | strFormat = strLine.Left(index); |
| | | |
| | | strRemark = strLine.Right(strLine.GetLength() - index - 1); |
| | | strRemark.Replace(L"\\r\\n", L"\r\n"); |
| | | |
| | | std::string sId = narrowFromW(strId); |
| | | std::string sName = narrowFromW(strName); |
| | | std::string sFormat = narrowFromW(strFormat); |
| | | std::string sRemark = narrowFromW(strRemark); |
| | | |
| | | SERVO::CDataVariable* pVarialble = new SERVO::CDataVariable( |
| | | sId.c_str(), |
| | | sName.c_str(), |
| | | sFormat.c_str(), |
| | | sRemark.c_str()); |
| | | dataVars.push_back(pVarialble); |
| | | } |
| | | |
| | | if (!dataVars.empty()) { |
| | | clearAllDataVariabel(); |
| | | for (auto item : dataVars) { |
| | | m_dataVariabels.push_back(item); |
| | | } |
| | | } |
| | | |
| | | return 0; |
| | | } |
| | | |
| | | std::vector<SERVO::CVariable*>& CHsmsPassive::getVariables() |
| | | { |
| | | return m_variabels; |
| | | } |
| | | |
| | | std::vector<SERVO::CDataVariable*>& CHsmsPassive::getDataVariables() |
| | | { |
| | | return m_dataVariabels; |
| | | } |
| | | |
| | | unsigned int CHsmsPassive::getMaxVariableId() const |
| | |
| | | return item; |
| | | } |
| | | } |
| | | // try numeric id string |
| | | if (pszName != nullptr && *pszName) { |
| | | const int id = atoi(pszName); |
| | | if (id > 0) { |
| | | return getVariable(id); |
| | | } |
| | | } |
| | | |
| | | return nullptr; |
| | | } |
| | | |
| | | SERVO::CDataVariable* CHsmsPassive::getDataVariable(int dvid) |
| | | { |
| | | for (auto item : m_dataVariabels) { |
| | | if (item->getVarialbleId() == (unsigned int)dvid) return item; |
| | | } |
| | | return nullptr; |
| | | } |
| | | |
| | | SERVO::CDataVariable* CHsmsPassive::getDataVariable(const char* pszName) |
| | | { |
| | | for (auto item : m_dataVariabels) { |
| | | if (item->getName().compare(pszName) == 0) return item; |
| | | } |
| | | return nullptr; |
| | | } |
| | | |
| | |
| | | delete item; |
| | | } |
| | | m_variabels.clear(); |
| | | } |
| | | |
| | | void CHsmsPassive::clearAllDataVariabel() |
| | | { |
| | | for (auto item : m_dataVariabels) { |
| | | delete item; |
| | | } |
| | | m_dataVariabels.clear(); |
| | | } |
| | | |
| | | CStringA WideToUtf8(const CStringW& ws) |
| | |
| | | } |
| | | file.Close(); |
| | | |
| | | return 0; |
| | | } |
| | | |
| | | int CHsmsPassive::writeDataVariablesToFile(const std::string& filepath) |
| | | { |
| | | if (filepath.empty()) return -3; |
| | | |
| | | CFile file; |
| | | if (!file.Open(filepath.c_str(), CFile::modeCreate | CFile::modeWrite | CFile::shareDenyNone)) { |
| | | return -3; |
| | | } |
| | | |
| | | const std::string headerAnsi = "DVID,DV Name,DV Format,DV Remark\r\n"; |
| | | if (m_bDataVariableUtf8) { |
| | | if (m_bDataVariableUtf8Bom) { |
| | | const BYTE bom[3] = { 0xEF, 0xBB, 0xBF }; |
| | | file.Write(bom, 3); |
| | | } |
| | | CStringA header = AnsiToUtf8(headerAnsi); |
| | | file.Write(header.GetString(), header.GetLength()); |
| | | } |
| | | else { |
| | | file.Write(headerAnsi.data(), (UINT)headerAnsi.size()); |
| | | } |
| | | |
| | | for (auto v : m_dataVariabels) { |
| | | if (v == nullptr) continue; |
| | | std::string lineAnsi; |
| | | lineAnsi.reserve(256); |
| | | lineAnsi += std::to_string(v->getVarialbleId()); |
| | | lineAnsi.push_back(','); |
| | | lineAnsi += v->getName(); |
| | | lineAnsi.push_back(','); |
| | | lineAnsi += SERVO::CVariable::formatToString(v->getFormat()); |
| | | lineAnsi.push_back(','); |
| | | lineAnsi += v->getRemark(); |
| | | lineAnsi.append("\r\n"); |
| | | |
| | | if (m_bDataVariableUtf8) { |
| | | CStringA outLine = AnsiToUtf8(lineAnsi); |
| | | file.Write(outLine.GetString(), outLine.GetLength()); |
| | | } |
| | | else { |
| | | file.Write(lineAnsi.data(), (UINT)lineAnsi.size()); |
| | | } |
| | | } |
| | | file.Close(); |
| | | return 0; |
| | | } |
| | | |
| | |
| | | else if (nStream == 1 && pHeader->function == 11) { |
| | | replyStatusVariableNamelistRequest(pMessage); |
| | | } |
| | | else if (nStream == 1 && pHeader->function == 21) { |
| | | replyDataVariableNamelistRequest(pMessage); |
| | | } |
| | | else if (nStream == 1 && pHeader->function == 23) { |
| | | replyCollectionEventNamelistRequest(pMessage); |
| | | } |
| | |
| | | return ER_NOERROR; |
| | | } |
| | | |
| | | // S1F21/S1F22 - Data Variable Namelist |
| | | int CHsmsPassive::replyDataVariableNamelistRequest(IMessage* pRecv) |
| | | { |
| | | if (m_pPassive == NULL || STATE::SELECTED != m_pPassive->getState()) { |
| | | return ER_NOTSELECT; |
| | | } |
| | | |
| | | std::vector<unsigned short> reqIds; |
| | | ISECS2Item* pBody = pRecv->getBody(); |
| | | if (pBody != nullptr && pBody->getType() == SITYPE::L) { |
| | | const int sz = pBody->getSubItemSize(); |
| | | for (int i = 0; i < sz; ++i) { |
| | | unsigned short id = 0; |
| | | if (pBody->getSubItemU2(i, id)) { |
| | | reqIds.push_back(id); |
| | | } |
| | | } |
| | | } |
| | | |
| | | std::vector<unsigned short> dvids; |
| | | std::set<unsigned short> requested(reqIds.begin(), reqIds.end()); |
| | | Lock(); |
| | | if (reqIds.empty()) { |
| | | for (auto v : m_dataVariabels) { |
| | | if (v) dvids.push_back(static_cast<unsigned short>(v->getVarialbleId())); |
| | | } |
| | | } |
| | | else { |
| | | for (auto id : requested) dvids.push_back(id); |
| | | } |
| | | Unlock(); |
| | | |
| | | IMessage* pMessage = NULL; |
| | | HSMS_Create1Message(pMessage, m_nSessionId, 1, 22, pRecv->getHeader()->systemBytes); |
| | | ASSERT(pMessage); |
| | | |
| | | ISECS2Item* pList = pMessage->getBody(); // L[n] of {DVID, DVNAME, UNITS} |
| | | for (auto id : dvids) { |
| | | ISECS2Item* pEntry = pList->addItem(); |
| | | pEntry->addU2Item(id, "DVID"); |
| | | SERVO::CDataVariable* v = getDataVariable((int)id); |
| | | if (v != nullptr) { |
| | | pEntry->addItem(v->getName().c_str(), "DVNAME"); |
| | | pEntry->addItem(v->getRemark().c_str(), "UNITS"); |
| | | } |
| | | else { |
| | | pEntry->addItem("", "DVNAME"); |
| | | pEntry->addItem("", "UNITS"); |
| | | } |
| | | } |
| | | |
| | | m_pPassive->sendMessage(pMessage); |
| | | LOGI("<HSMS>[SEND]sessionId:%d, sType:%d systemBytes:%d", |
| | | pMessage->getHeader()->sessionId, pMessage->getHeader()->sType, pMessage->getHeader()->systemBytes); |
| | | LogSecsMessageBrief("<HSMS>[SEND]", pMessage); |
| | | HSMS_Destroy1Message(pMessage); |
| | | |
| | | return ER_NOERROR; |
| | | } |
| | | |
| | | // S1F23 |
| | | int CHsmsPassive::replyCollectionEventNamelistRequest(IMessage* pRecv) |
| | | { |