#include "iobox_api.h" #include "cJSON.h" #include #include #include "mbedtls/sha256.h" #include "mbedtls/aes.h" #include "mbedtls/base64.h" #include #include #include #include // Implementation: #include #include #include namespace ANSCENTER { iobox_api::iobox_api(const std::string& ip_mcast, int port_mcast) { macToIpMap.clear(); ipToDeviceMap.clear(); this->ip_mcast = ip_mcast; this->port_mcast = port_mcast; _username = "admin"; _password = "1234"; _port = 502; WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { this->_logger.LogError("iobox_api::iobox_api. WSAStartup failed with error: ", std::to_string(WSAGetLastError()), __FILE__, __LINE__); return; } } iobox_api::~iobox_api() noexcept { // Cancel all active toggle operations before destroying // This prevents detached threads from accessing a destroyed object { std::lock_guard mapLock(toggleMapMutex); for (auto& pair : activeToggles) { auto toggleInfo = pair.second; if (toggleInfo) { std::lock_guard infoLock(toggleInfo->mtx); toggleInfo->cancelled = true; toggleInfo->active = false; toggleInfo->cv.notify_one(); } } activeToggles.clear(); } // Close all open TCP sockets before clearing profiles { std::lock_guard lock(this->_mutex); for (auto& profile : this->iobox_profiles) { if (profile.sock_tcp != INVALID_SOCKET) { closesocket(profile.sock_tcp); profile.sock_tcp = INVALID_SOCKET; } } this->iobox_profiles.clear(); } macToIpMap.clear(); ipToDeviceMap.clear(); WSACleanup(); } bool isStringExistInVectorString(const std::vector& vec, const std::string& str) { try { for (const auto& item : vec) { if (item.find(str) != std::string::npos) { std::cout << "Found partial match: " << str << " in " << item << std::endl; return true; } } return false; } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return false; } } void printBufferAsHex(const char* buffer, size_t length) { try { for (size_t i = 0; i < length; ++i) { printf("%02X ", static_cast(buffer[i])); if ((i + 1) % 16 == 0) { printf("\n"); } } printf("\n"); } catch (const std::exception& e) { std::cerr << e.what() << std::endl; } } void printStringAsHex(const std::string& str) { try { for (size_t i = 0; i < str.size(); ++i) { printf("%02X ", static_cast(str[i])); if ((i + 1) % 16 == 0) { printf("\n"); } } printf("\n"); } catch (const std::exception& e) { std::cerr << e.what() << std::endl; } } std::string messageEncrypt(char* input, size_t input_len, const char* key) { #if MESSAGE_CRYPT_ENABLE == 0 return std::string(input, input_len); #else mbedtls_aes_context aes; char iv[17] = "1234567890123456"; size_t padding_len = 16 - (input_len % 16); if (padding_len == 0) { padding_len = 16; } size_t padded_len = input_len + padding_len; std::vector padded_input(padded_len); memcpy(padded_input.data(), input, input_len); for (size_t i = input_len; i < padded_len; ++i) { padded_input[i] = static_cast(padding_len); } //init vector to hold data output std::vector output(padded_len); std::string base64_output; mbedtls_aes_init(&aes); if (mbedtls_aes_setkey_enc(&aes, (const unsigned char*)key, 128) != 0) { std::cerr << "Failed to set key" << std::endl; mbedtls_aes_free(&aes); //return empty vector return base64_output; } if (mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_ENCRYPT, padded_len, (unsigned char*)iv, (const unsigned char*)padded_input.data(), (unsigned char*)output.data()) != 0) { std::cerr << "Failed to encrypt" << std::endl; mbedtls_aes_free(&aes); return base64_output; } mbedtls_aes_free(&aes); //encode to base64 base64_output.resize(output.size() * 2); size_t base64_len = 0; if (mbedtls_base64_encode((unsigned char*)base64_output.data(), base64_output.size(), &base64_len, (const unsigned char*)output.data(), output.size()) != 0) { std::cerr << "Failed to encode to base64" << std::endl; return base64_output; } base64_output.resize(base64_len); // std::cout << "Base64 output: " << base64_output << std::endl; return base64_output; #endif } std::string messageDecrypt(char* input, size_t input_len, const char* key) { #if MESSAGE_CRYPT_ENABLE == 0 return std::string(input, input_len); #else mbedtls_aes_context aes; //base64 decode std::vector base64_decoded(input_len); size_t base64_decoded_len = 0; if (mbedtls_base64_decode((unsigned char*)base64_decoded.data(), base64_decoded.size(), &base64_decoded_len, (const unsigned char*)input, input_len) != 0) { std::cerr << "Failed to decode base64" << std::endl; return std::string(); } base64_decoded.resize(base64_decoded_len); // std::cout << "Base64 decoded: " << base64_decoded.size() << std::endl; char iv[17] = "1234567890123456"; if (base64_decoded_len % 16 != 0) { std::cerr << "Invalid input length" << std::endl; return std::string(); } std::vector output(base64_decoded_len); mbedtls_aes_init(&aes); if (mbedtls_aes_setkey_dec(&aes, (const unsigned char*)key, 128) != 0) { std::cerr << "Failed to set key" << std::endl; mbedtls_aes_free(&aes); return std::string(); } if (mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_DECRYPT, base64_decoded_len, (unsigned char*)iv, (const unsigned char*)base64_decoded.data(), (unsigned char*)output.data()) != 0) { std::cerr << "Failed to decrypt" << std::endl; mbedtls_aes_free(&aes); return std::string(); } mbedtls_aes_free(&aes); // Remove PKCS5 padding unsigned char padding_len = output.back(); if (padding_len > 0 && padding_len <= 16) { // std::cout << "Padding length: " << (int)padding_len << std::endl; // printBufferAsHex(output.data(), output.size()); output.resize(output.size() - padding_len); } else { std::cerr << "Invalid padding length" << std::endl; return std::string();; } //copy to string std::string output_str(output.data(), output.size()); // std::cout << "Decrypted: " << output_str << std::endl; return output_str; #endif } std::string stringToHexString(const std::string& str) { // std::stringstream ss; // for (size_t i = 0; i < str.length(); ++i) { // ss << std::hex << std::setw(2) << std::setfill('0') << (int)str[i]; // } // return ss.str(); const char* hex_chars = "0123456789ABCDEF"; std::string hex_str; hex_str.reserve(str.length() * 2); for (unsigned char c : str) { hex_str.push_back(hex_chars[c >> 4]); hex_str.push_back(hex_chars[c & 0x0F]); } return hex_str; } bool isValidIp(const std::string& ip) { struct sockaddr_in sa; return inet_pton(AF_INET, ip.c_str(), &(sa.sin_addr)) != 0; } std::string hostNameToIp(std::string hostName) { struct hostent* host; struct in_addr addr; if ((host = gethostbyname(hostName.c_str())) == NULL) { std::cerr << "Failed to get host by name: " << hostName << std::endl; return ""; } addr.s_addr = *(u_long*)host->h_addr_list[0]; return inet_ntoa(addr); } std::string checkIpFromRemote(const std::string& remote) { std::string ip; if (!isValidIp(remote)) { std::cout << "Host name address: " << remote << std::endl; ip = hostNameToIp(remote); if (ip == "") { std::cerr << "Failed to get ip from host name: " << remote << std::endl; return ""; } return ip; } else return remote; } iobox_info_t parseIoboxResponseObj(cJSON* responseObj) { try { iobox_info_t info; cJSON* model = cJSON_GetObjectItem(responseObj, "Model"); if (model != nullptr) { info.model = model->valuestring; } cJSON* serial = cJSON_GetObjectItem(responseObj, "Serial"); if (serial != nullptr) { info.serial_number = serial->valuestring; } cJSON* hwVer = cJSON_GetObjectItem(responseObj, "HwVer"); if (hwVer != nullptr) { info.hardware_version = hwVer->valuestring; } cJSON* fwVer = cJSON_GetObjectItem(responseObj, "FwVer"); if (fwVer != nullptr) { info.firmware_version = fwVer->valuestring; } cJSON* macAddr = cJSON_GetObjectItem(responseObj, "macAddr"); if (macAddr != nullptr) { info.mac_address = macAddr->valuestring; } cJSON* localIp = cJSON_GetObjectItem(responseObj, "localIp"); if (localIp != nullptr) { info.ip_address = localIp->valuestring; } return info; } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return iobox_info_t(); } } iobox_info_t parseIoboxResponse(const std::string& response) { try { //exp: {"response": { "Model":"ANS IOBOX","Serial":"IO123","HwVer":"11", "FwVer":"22","macAddr":"a2c3d4e5f6","localIp":"192.168.110.121"}} iobox_info_t info; cJSON* root = cJSON_Parse(response.c_str()); if (root == nullptr) { std::cerr << "Failed to parse JSON" << std::endl; return info; } cJSON* responseObj = cJSON_GetObjectItem(root, "response"); if (responseObj == nullptr) { std::cerr << "Failed to get response object" << std::endl; cJSON_Delete(root); return info; } info = parseIoboxResponseObj(responseObj); cJSON_Delete(root); std::cout << "Parsed IOBox Info:" << std::endl; std::cout << " Model: " << info.model << std::endl; std::cout << " Serial Number: " << info.serial_number << std::endl; std::cout << " Hardware Version: " << info.hardware_version << std::endl; std::cout << " Firmware Version: " << info.firmware_version << std::endl; std::cout << " MAC Address: " << info.mac_address << std::endl; std::cout << " IP Address: " << info.ip_address << std::endl; std::cout << " Public IP Address: " << info.ip_public_address << std::endl; return info; } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return iobox_info_t(); } } void show_info_iobox(iobox_info_t iobox_info) { std::cout << " IP Address: " << iobox_info.ip_address << std::endl; } void iobox_api::show_profile_iobox(const std::string& remote) { try { std::string ip = checkIpFromRemote(remote); if (ip == "") { this->_logger.LogError("iobox_api::show_profile_iobox. Failed to get ip from remote: ", remote, __FILE__, __LINE__); return; } iobox_profile_t* profile = findProfileByIp(ip); if (profile == nullptr) { this->_logger.LogError("iobox_api::show_profile_iobox. IP address not found: ", ip, __FILE__, __LINE__); return; } show_profile_iobox(*profile); } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::show_profile_iobox. ", e.what(), __FILE__, __LINE__); } } void iobox_api::show_profile_iobox(const iobox_profile_t& profile) { try { // std::cout << "Profile ioboxs: " << this->iobox_profiles.size() << std::endl; // for (const iobox_profile_t& profile : this->iobox_profiles) { // std::cout << "* Index: " << &profile - &this->iobox_profiles[0] << std::endl; show_info_iobox(profile.iobox_info); std::cout << " Is connected: " << profile.is_connected << std::endl; std::cout << " Is anthenticated: " << profile.is_anthenticated << std::endl; std::cout << " TCP Socket: " << profile.sock_tcp << std::endl; std::cout << " Counting get: " << profile.counting_get << std::endl; std::cout << " Model: " << profile.iobox_info.model << std::endl; std::cout << " Serial Number: " << profile.iobox_info.serial_number << std::endl; std::cout << " Hardware Version: " << profile.iobox_info.hardware_version << std::endl; std::cout << " Firmware Version: " << profile.iobox_info.firmware_version << std::endl; std::cout << " MAC Address: " << profile.iobox_info.mac_address << std::endl; std::cout << " IP Address: " << profile.iobox_info.ip_address << std::endl; std::cout << " Public IP Address: " << profile.iobox_info.ip_public_address << std::endl; } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::show_profile_iobox. ", e.what(), __FILE__, __LINE__); } } void iobox_api::show_profile_ioboxs() { try { if (this->iobox_profiles.size() == 0) { this->_logger.LogError("iobox_api::show_profile_iobox. ", "No iobox profiles", __FILE__, __LINE__); return; } for (const auto& profile : this->iobox_profiles) { std::cout << "* Index: " << &profile - &this->iobox_profiles[0] << std::endl; show_profile_iobox(profile); } } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::show_profile_ioboxs. ", e.what(), __FILE__, __LINE__); } } void iobox_api::save_info_iobox(const iobox_info_t& iobox_info) { try { iobox_profile_t profile; profile.counting_get = 0; profile.is_connected = false; profile.is_anthenticated = false; profile.sock_tcp = INVALID_SOCKET; profile.iobox_info = iobox_info; this->iobox_profiles.push_back(profile); std::cout << "Save iobox, num device current: " << this->iobox_profiles.size() << std::endl; } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::save_info_iobox. ", e.what(), __FILE__, __LINE__); } } void iobox_api::remove_last_info_iobox() { try { if (this->iobox_profiles.size() == 0) { this->_logger.LogError("iobox_api::remove_last_info_iobox. ", "No iobox profiles", __FILE__, __LINE__); return; } this->iobox_profiles.pop_back(); std::cout << "Remove last iobox, num device current: " << this->iobox_profiles.size() << std::endl; } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::remove_last_info_iobox. ", e.what(), __FILE__, __LINE__); } } bool iobox_api::sendTcpMessage(const std::string& ip, const char* buffer, size_t length, bool needCheckAuthen) { try { for (auto& profile : this->iobox_profiles) { if ((profile.iobox_info.ip_address == ip || profile.iobox_info.ip_public_address == ip) && profile.is_connected && profile.sock_tcp != INVALID_SOCKET) { if (needCheckAuthen && !profile.is_anthenticated) { this->_logger.LogError("iobox_api::sendTcpMessage. ", "Please anthenticate before send message", __FILE__, __LINE__); return false; } std::string encrypted = messageEncrypt((char*)buffer, length, this->aes_key); if (encrypted.size() == 0) { this->_logger.LogError("iobox_api::sendTcpMessage. ", "Failed to encrypt message", __FILE__, __LINE__); return false; } char buffer_read[1024]; setSocketTimeout(profile.sock_tcp, 10); int result_read; do { result_read = recv(profile.sock_tcp, buffer_read, sizeof(buffer_read), 0); } while (result_read > 0); if (result_read == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK) { // std::cerr << "recv failed with error: " << WSAGetLastError() << std::endl; } else if (result_read == 0) { this->_logger.LogError("iobox_api::sendTcpMessage. Connection closed by peer: ", ip, __FILE__, __LINE__); return false; } else { std::cout << "Cleared TCP receiver buffer for IP: " << ip << std::endl; } int result = send(profile.sock_tcp, encrypted.data(), (int)encrypted.size(), 0); if (result == SOCKET_ERROR) { int wsaErr = WSAGetLastError(); if (wsaErr == WSAECONNRESET || wsaErr == WSAENOTCONN) { this->_logger.LogError("iobox_api::sendTcpMessage. Connection reset or not connected: ", ip, __FILE__, __LINE__); } else { this->_logger.LogError("iobox_api::sendTcpMessage. Send failed with error: ", std::to_string(wsaErr), __FILE__, __LINE__); } return false; } std::cout << "Sent " << length << " bytes message to " << ip << std::endl; std::cout << "Message: " << buffer << std::endl; std::cout << "Sent encrypt " << encrypted.size() << " bytes message to " << ip << std::endl; std::cout << "Message encrypt: " << encrypted.data() << std::endl; // printBufferAsHex(buffer, length); return true; } } this->_logger.LogError("iobox_api::sendTcpMessage. IP address not found or not connected: ", ip, __FILE__, __LINE__); return false; } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::sendTcpMessage. ", e.what(), __FILE__, __LINE__); return false; } } bool iobox_api::receiveTcpMessage(const std::string& ip, char* buffer, size_t& length, int timeout) { try { if (length == 0) { this->_logger.LogError("iobox_api::receiveTcpMessage. ", "Buffer length is zero", __FILE__, __LINE__); return false; } size_t bufferCapacity = length; // Save original buffer capacity for (auto& profile : this->iobox_profiles) { if ((profile.iobox_info.ip_address == ip || profile.iobox_info.ip_public_address == ip) && profile.is_connected && profile.sock_tcp != INVALID_SOCKET) { setSocketTimeout(profile.sock_tcp, timeout); // Reserve 1 byte for null terminator to prevent buffer overflow int recvLen = recv(profile.sock_tcp, buffer, (int)(bufferCapacity - 1), 0); if (recvLen > 0) { length = recvLen; buffer[length] = '\0'; std::cout << "Received message: " << length << " bytes" << std::endl; std::cout << "Message: " << buffer << std::endl; // printBufferAsHex(buffer, recvLen); std::string decrypted = messageDecrypt(buffer, length, this->aes_key); if (decrypted.empty()) { this->_logger.LogError("iobox_api::receiveTcpMessage. ", "Failed to decrypt message", __FILE__, __LINE__); return false; } // Ensure decrypted data fits in buffer before copying if (decrypted.size() >= bufferCapacity) { this->_logger.LogError("iobox_api::receiveTcpMessage. ", "Decrypted message too large for buffer", __FILE__, __LINE__); return false; } //copy decrypted data to buffer memcpy(buffer, decrypted.data(), decrypted.size()); length = decrypted.size(); buffer[length] = '\0'; std::cout << "Decrypt length: " << length << " bytes" << std::endl; std::cout << "Decrypt message: " << decrypted << std::endl; return true; } else if (recvLen == 0) { this->_logger.LogError("iobox_api::receiveTcpMessage. Connection closed by peer: ", ip, __FILE__, __LINE__); return false; } else if (recvLen == SOCKET_ERROR && WSAGetLastError() == WSAETIMEDOUT) { this->_logger.LogError("iobox_api::receiveTcpMessage. Recv timeout: ", ip, __FILE__, __LINE__); return false; } else { this->_logger.LogError("iobox_api::receiveTcpMessage. Recv failed with error: ", std::to_string(WSAGetLastError()), __FILE__, __LINE__); return false; } } } this->_logger.LogError("iobox_api::receiveTcpMessage. IP address not found or not connected: ", ip, __FILE__, __LINE__); return false; } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::receiveTcpMessage. ", e.what(), __FILE__, __LINE__); return false; } } void iobox_api::sendUnicastMessage(SOCKET sock, const char* ip, const std::string& message) { struct sockaddr_in unicastAddr; memset(&unicastAddr, 0, sizeof(unicastAddr)); unicastAddr.sin_family = AF_INET; unicastAddr.sin_addr.s_addr = inet_addr(ip); unicastAddr.sin_port = htons(this->port_mcast); if (sendto(sock, message.c_str(), message.length(), 0, (struct sockaddr*)&unicastAddr, sizeof(unicastAddr)) == SOCKET_ERROR) { this->_logger.LogError("iobox_api::sendUnicastMessage. Sendto failed with error: ", std::to_string(WSAGetLastError()), __FILE__, __LINE__); } else { std::cout << "Sent message: " << message << std::endl; } } void iobox_api::sendMulticastMessage(const std::string& message) { try { SOCKET sock = INVALID_SOCKET; struct sockaddr_in multicastAddr; sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sock == INVALID_SOCKET) { this->_logger.LogError("iobox_api::sendMulticastMessage. Socket failed with error: ", std::to_string(WSAGetLastError()), __FILE__, __LINE__); // WSACleanup(); return; } memset(&multicastAddr, 0, sizeof(multicastAddr)); multicastAddr.sin_family = AF_INET; multicastAddr.sin_addr.s_addr = inet_addr(this->ip_mcast.c_str()); multicastAddr.sin_port = htons(this->port_mcast); if (sendto(sock, message.c_str(), message.length(), 0, (struct sockaddr*)&multicastAddr, sizeof(multicastAddr)) == SOCKET_ERROR) { this->_logger.LogError("iobox_api::sendMulticastMessage. Sendto failed with error: ", std::to_string(WSAGetLastError()), __FILE__, __LINE__); } else { std::cout << "Sent message: " << message << std::endl; } closesocket(sock); } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::sendMulticastMessage. ", e.what(), __FILE__, __LINE__); } } void iobox_api::setSocketTimeout(SOCKET sock, int timeout) { try { // On Windows, SO_RCVTIMEO expects a DWORD value in milliseconds, not struct timeval. // Callers already pass millisecond values (e.g., 3000 = 3s, 10000 = 10s). DWORD timeoutMs = static_cast(timeout); if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeoutMs, sizeof(timeoutMs)) < 0) { this->_logger.LogError("iobox_api::setSocketTimeout. Setsockopt failed with error: ", std::to_string(WSAGetLastError()), __FILE__, __LINE__); } } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::setSocketTimeout. ", e.what(), __FILE__, __LINE__); } } std::string toUpperCase(const std::string& str) { std::string result = str; std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { return std::toupper(c); }); return result; } std::string createAliasName(const std::string& serial, const std::string& hwVer, const std::string& fwVer, uint16_t index) { return "SN" + toUpperCase(serial) + "H" + hwVer + "F" + fwVer + "I" + std::to_string(index); } std::string createMulticastAlias(const std::string& aliasName, const std::string& model, const std::string& serial, const std::string& ip) { //exp: Alias-Model-SN-IP return aliasName + "-" + model + "-" + toUpperCase(serial) + "-" + ip; } uint16_t iobox_api::getIndexIoboxFromIp(const std::string& ip) { try { for (const auto& profile : this->iobox_profiles) { if (profile.iobox_info.ip_address == ip || profile.iobox_info.ip_public_address == ip) { return &profile - &this->iobox_profiles[0] + 1; } } return -1; } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return -1; } } void logNetworkAdapters(network_adapter_t adapter) { std::cout << "Adapter Index: " << adapter.adapter_index << std::endl; std::cout << "Adapter Name: " << adapter.adapter_name << std::endl; std::cout << "Adapter Description: " << adapter.adapter_description << std::endl; std::cout << "Adapter MAC: " << adapter.adapter_mac << std::endl; std::cout << "Adapter IP: " << adapter.adapter_ip << std::endl; std::cout << "Adapter Subnet: " << adapter.adapter_subnet << std::endl; std::cout << "Adapter Gateway: " << adapter.adapter_gateway << std::endl; } std::vector iobox_api::getNetworkAdapters() { std::vector adapters; PIP_ADAPTER_INFO pAdapterInfo; PIP_ADAPTER_INFO pAdapter = NULL; ULONG ulOutBufLen = sizeof(IP_ADAPTER_INFO); DWORD dwRetVal = 0; pAdapterInfo = (IP_ADAPTER_INFO*)malloc(sizeof(IP_ADAPTER_INFO)); if (pAdapterInfo == NULL) { this->_logger.LogError("iobox_api::getNetworkAdapters. ", "Error allocating memory needed to call GetAdaptersinfo", __FILE__, __LINE__); return adapters; } if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) { free(pAdapterInfo); pAdapterInfo = (IP_ADAPTER_INFO*)malloc(ulOutBufLen); if (pAdapterInfo == NULL) { this->_logger.LogError("iobox_api::getNetworkAdapters. ", "Error allocating memory needed to call GetAdaptersinfo", __FILE__, __LINE__); return adapters; } } if ((dwRetVal = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) == NO_ERROR) { pAdapter = pAdapterInfo; while (pAdapter) { if (strcmp(pAdapter->IpAddressList.IpAddress.String, "0.0.0.0") == 0) { pAdapter = pAdapter->Next; continue; } network_adapter_t adapter; adapter.adapter_index = std::to_string(pAdapter->Index); adapter.adapter_name = pAdapter->AdapterName; adapter.adapter_description = pAdapter->Description; std::string mac((char*)pAdapter->Address, pAdapter->AddressLength); adapter.adapter_mac = stringToHexString(mac); adapter.adapter_ip = pAdapter->IpAddressList.IpAddress.String; adapter.adapter_subnet = pAdapter->IpAddressList.IpMask.String; adapter.adapter_gateway = pAdapter->GatewayList.IpAddress.String; adapters.push_back(adapter); pAdapter = pAdapter->Next; } } else { this->_logger.LogError("iobox_api::getNetworkAdapters. GetAdaptersInfo failed with error", std::to_string(dwRetVal), __FILE__, __LINE__); } if (pAdapterInfo) { free(pAdapterInfo); } for (const auto& adapter : adapters) { logNetworkAdapters(adapter); } return adapters; } std::vector iobox_api::CreateDeviceChannel(const std::string& multicastInfo) { // We manually create the device channels based on the multicastInfo std::vector devices; std::string DO1Channel = multicastInfo + "-DO1"; std::string DO2Channel = multicastInfo + "-DO2"; std::string DO3Channel = multicastInfo + "-DO3"; std::string DO4Channel = multicastInfo + "-DO4"; devices.push_back(DO1Channel); devices.push_back(DO2Channel); devices.push_back(DO3Channel); devices.push_back(DO4Channel); std::string DI1Channel = multicastInfo + "-DI1"; std::string DI2Channel = multicastInfo + "-DI2"; std::string DI3Channel = multicastInfo + "-DI3"; std::string DI4Channel = multicastInfo + "-DI4"; devices.push_back(DI1Channel); devices.push_back(DI2Channel); devices.push_back(DI3Channel); devices.push_back(DI4Channel); std::string AI1Channel = multicastInfo + "-AI1"; std::string AI2Channel = multicastInfo + "-AI2"; devices.push_back(AI1Channel); devices.push_back(AI2Channel); return devices; } std::vector iobox_api::scanNetworkDevicesMulticast(int timeout) { std::lock_guard lock(_mutex); try { std::vector devices; for (const auto& item : this->iobox_profiles) { if (item.is_connected || item.sock_tcp != INVALID_SOCKET) { std::cout << "Please close connection to " << item.iobox_info.ip_address << "(public: " << item.iobox_info.ip_public_address << ") " << "befor new scan" << std::endl; // return devices; if (!item.iobox_info.ip_public_address.empty()) { disconnectToIobox(item.iobox_info.ip_public_address); } else { disconnectToIobox(item.iobox_info.ip_address); } } } //remove all elements from iobox_profiles before and we start with new scan this->iobox_profiles.clear(); // WSADATA wsaData; SOCKET sock = INVALID_SOCKET; struct sockaddr_in multicastAddr; struct ip_mreq multicastRequest; char recvBuf[1024]; struct sockaddr_in recvAddr; int recvAddrLen = sizeof(recvAddr); std::string message = "{\"request\":\"Discovery\"}"; // if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { // std::cerr << "WSAStartup failed with error: " << WSAGetLastError() << std::endl; // return devices; // } sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sock == INVALID_SOCKET) { this->_logger.LogError("iobox_api::scanNetworkDevicesMulticast. Socket failed with error:", std::to_string(WSAGetLastError()), __FILE__, __LINE__); // WSACleanup(); return devices; } memset(&multicastAddr, 0, sizeof(multicastAddr)); multicastAddr.sin_family = AF_INET; multicastAddr.sin_addr.s_addr = htonl(INADDR_ANY); multicastAddr.sin_port = htons(this->port_mcast); if (bind(sock, (struct sockaddr*)&multicastAddr, sizeof(multicastAddr)) == SOCKET_ERROR) { this->_logger.LogError("iobox_api::scanNetworkDevicesMulticast. Bind failed with error:", std::to_string(WSAGetLastError()), __FILE__, __LINE__); closesocket(sock); // WSACleanup(); return devices; } multicastRequest.imr_multiaddr.s_addr = inet_addr(this->ip_mcast.c_str()); multicastRequest.imr_interface.s_addr = htonl(INADDR_ANY); #ifndef TEST_FIX_IP // if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&multicastRequest, sizeof(multicastRequest)) < 0) { // std::cerr << "setsockopt failed with error: " << WSAGetLastError() << std::endl; // closesocket(sock); // // WSACleanup(); // return devices; // } #endif setSocketTimeout(sock, 3000); auto start = std::chrono::steady_clock::now(); sendMulticastMessage(message); while (std::chrono::steady_clock::now() - start < std::chrono::seconds(timeout)) { int recvLen = recvfrom(sock, recvBuf, sizeof(recvBuf) - 1, 0, (struct sockaddr*)&recvAddr, &recvAddrLen); if (recvLen > 0) { recvBuf[recvLen] = '\0'; std::string message_rsp(recvBuf); std::string senderIp = inet_ntoa(recvAddr.sin_addr); std::cout << "Received message: \"" << message_rsp << "\" from " << senderIp << std::endl; iobox_info_t info = parseIoboxResponse(message_rsp); if (info.ip_address == senderIp && info.ip_address != "" && !isStringExistInVectorString(devices, senderIp)) { std::string aliasName = createAliasName(info.serial_number, info.hardware_version, info.firmware_version, devices.size() + 1); std::string multicastAlias = createMulticastAlias(aliasName, info.model, info.serial_number, info.ip_address); std::cout << "multicast alias: " << multicastAlias << std::endl; devices.push_back(multicastAlias); save_info_iobox(info); // add to macToIpMap std::string mac = info.serial_number; std::string ip = info.ip_address; if (macToIpMap.find(mac) == macToIpMap.end()) { macToIpMap[mac] = ip; std::cout << "Added: " << mac << " -> " << ip << std::endl; } else { std::cout << "MAC already exists. Skipping insert.\n"; } std::vector deviceChannels = CreateDeviceChannel(multicastAlias); // add to ipToDeviceMap if (ipToDeviceMap.find(ip) == ipToDeviceMap.end()) { ipToDeviceMap[ip] = deviceChannels; } else { std::cout << "ip already exists. Skipping insert.\n"; } } } else { // std::cerr << "recvfrom failed with error: " << WSAGetLastError() << std::endl; // sendMulticastMessage(message); } } #ifndef TEST_FIX_IP // setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char*)&multicastRequest, sizeof(multicastRequest)); #endif closesocket(sock); // WSACleanup(); // Check if there are any devices foun. Otherwise, use unicast scan if (devices.empty()) { std::cout << "No devices found using multicast scan. Trying unicast scan..." << std::endl; return scanNetworkDevicesManually(timeout); } return devices; } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::scanNetworkDevicesMulticast. ", e.what(), __FILE__, __LINE__); return std::vector(); } } std::vector iobox_api::scanNetworkDevicesManuallyOnNetworkAdapter(network_adapter_t adapter, int timeout) { std::vector devices; for (const auto& item : this->iobox_profiles) { if (item.is_connected || item.sock_tcp != INVALID_SOCKET) { std::cout << "Please close connection to " << item.iobox_info.ip_address << " (public: " << item.iobox_info.ip_public_address << ") " << "befor new scan" << std::endl; // return devices; if (!item.iobox_info.ip_public_address.empty()) { disconnectToIobox(item.iobox_info.ip_public_address); } else { disconnectToIobox(item.iobox_info.ip_address); } } } //remove all elements from iobox_profiles before and we start with new scan this->iobox_profiles.clear(); // WSADATA wsaData; SOCKET sock = INVALID_SOCKET; struct sockaddr_in unicastAddress; struct ip_mreq multicastRequest; char recvBuf[1024]; struct sockaddr_in recvAddr; int recvAddrLen = sizeof(recvAddr); std::string message = "{\"request\":\"Discovery\"}"; // if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { // std::cerr << "WSAStartup failed with error: " << WSAGetLastError() << std::endl; // return devices; // } sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sock == INVALID_SOCKET) { std::cerr << "socket failed with error: " << WSAGetLastError() << std::endl; // WSACleanup(); return devices; } memset(&unicastAddress, 0, sizeof(unicastAddress)); unicastAddress.sin_family = AF_INET; unicastAddress.sin_addr.s_addr = htonl(INADDR_ANY); unicastAddress.sin_port = htons(this->port_mcast); if (bind(sock, (struct sockaddr*)&unicastAddress, sizeof(unicastAddress)) == SOCKET_ERROR) { this->_logger.LogError("iobox_api::scanNetworkDevicesManuallyOnNetworkAdapter. Bind failed with error:", std::to_string(WSAGetLastError()), __FILE__, __LINE__); closesocket(sock); // WSACleanup(); return devices; } setSocketTimeout(sock, 10); auto start = std::chrono::steady_clock::now(); // Calculate the network address and broadcast address struct in_addr ip_addr, subnet_mask, network_addr, broadcast_addr; inet_pton(AF_INET, adapter.adapter_ip.c_str(), &ip_addr); inet_pton(AF_INET, adapter.adapter_subnet.c_str(), &subnet_mask); network_addr.s_addr = ip_addr.s_addr & subnet_mask.s_addr; broadcast_addr.s_addr = network_addr.s_addr | ~subnet_mask.s_addr; std::cout << "Network address: " << inet_ntoa(network_addr) << std::endl; std::cout << "Broadcast address: " << inet_ntoa(broadcast_addr) << std::endl; // Iterate over all possible host addresses in the subnet SOCKET sockSend = INVALID_SOCKET; sockSend = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sockSend == INVALID_SOCKET) { this->_logger.LogError("iobox_api::scanNetworkDevicesManuallyOnNetworkAdapter. Socket failed with error:", std::to_string(WSAGetLastError()), __FILE__, __LINE__); closesocket(sock); // WSACleanup(); return devices; } for (uint32_t host = ntohl(network_addr.s_addr) + 1; host < ntohl(broadcast_addr.s_addr); ++host) { struct in_addr host_addr; host_addr.s_addr = htonl(host); std::string host_ip = inet_ntoa(host_addr); if (host_ip == adapter.adapter_ip) { continue; } // Send unicast message to each host sendUnicastMessage(sockSend, host_ip.c_str(), message); if (std::chrono::steady_clock::now() - start < std::chrono::seconds(timeout)) { int recvLen = recvfrom(sock, recvBuf, sizeof(recvBuf) - 1, 0, (struct sockaddr*)&recvAddr, &recvAddrLen); if (recvLen > 0) { recvBuf[recvLen] = '\0'; std::string message_rsp(recvBuf); std::string senderIp = inet_ntoa(recvAddr.sin_addr); std::cout << "Received message: \"" << message_rsp << "\" from " << senderIp << std::endl; iobox_info_t info = parseIoboxResponse(message_rsp); if (info.ip_address == senderIp && info.ip_address != "" && !isStringExistInVectorString(devices, senderIp)) { std::string aliasName = createAliasName(info.serial_number, info.hardware_version, info.firmware_version, devices.size() + 1); std::string multicastAlias = createMulticastAlias(aliasName, info.model, info.serial_number, info.ip_address); std::cout << "unicast alias: " << multicastAlias << std::endl; devices.push_back(multicastAlias); save_info_iobox(info); // add to macToIpMap std::string mac = info.serial_number; std::string ip = info.ip_address; if (macToIpMap.find(mac) == macToIpMap.end()) { macToIpMap[mac] = ip; std::cout << "Added: " << mac << " -> " << ip << std::endl; } else { std::cout << "MAC already exists. Skipping insert.\n"; } std::vector deviceChannels = CreateDeviceChannel(multicastAlias); // add to ipToDeviceMap if (ipToDeviceMap.find(ip) == ipToDeviceMap.end()) { ipToDeviceMap[ip] = deviceChannels; } else { std::cout << "ip already exists. Skipping insert.\n"; } } continue; } else { std::cout << "timeout for IP: " << host_ip << ", scan next" << std::endl; continue; // std::cerr << "recvfrom failed with error: " << WSAGetLastError() << std::endl; // sendMulticastMessage(message); } } else { break; } } closesocket(sockSend); closesocket(sock); // WSACleanup(); return devices; } std::vector iobox_api::scanNetworkDevicesManually(int timeout) { std::vector adapters; adapters = getNetworkAdapters(); if (adapters.size() == 0) { std::cerr << "Failed to get network adapters" << std::endl; this->_logger.LogError("iobox_api::scanNetworkDevicesManually.", "Failed to get network adapters", __FILE__, __LINE__); return std::vector(); } std::vector devices; for (const auto& adapter : adapters) { std::cout << "\nAdapter: " << adapter.adapter_name << std::endl; std::vector devices_adapter = scanNetworkDevicesManuallyOnNetworkAdapter(adapter, timeout); devices.insert(devices.end(), devices_adapter.begin(), devices_adapter.end()); } for (const auto& device : devices) { std::cout << "Device: " << device << std::endl; } return devices; } bool parseResponeCommon(const std::string& response, const std::string& action, std::string& getResult, std::string& getReason, iobox_info_t& iobox_info) { try { cJSON* root = cJSON_Parse(response.c_str()); if (root == nullptr) { std::cerr << "Failed to parse JSON" << std::endl; return false; } cJSON* responseObj = cJSON_GetObjectItem(root, "response"); if (responseObj == nullptr) { std::cerr << "Failed to get response object" << std::endl; cJSON_Delete(root); return false; } cJSON* actionResponse = cJSON_GetObjectItem(responseObj, "action"); if (actionResponse == nullptr || strcmp(actionResponse->valuestring, action.c_str()) != 0) { std::cerr << "Action mismatch or not found" << std::endl; cJSON_Delete(root); return false; } cJSON* resultResponse = cJSON_GetObjectItem(responseObj, "result"); if (resultResponse == nullptr) { std::cerr << "Failed to get result" << std::endl; cJSON_Delete(root); return false; } if (resultResponse->type == cJSON_Array) { char* result = cJSON_PrintUnformatted(resultResponse); std::cout << "Result: " << result << std::endl; std::string resultStr(result); cJSON_free(result); getResult = resultStr; } else if (resultResponse->type == cJSON_String) { getResult = resultResponse->valuestring; } if (cJSON_HasObjectItem(responseObj, "info")) { cJSON* infoResponse = cJSON_GetObjectItem(responseObj, "info"); if (infoResponse == nullptr) { std::cerr << "Failed to get info" << std::endl; cJSON_Delete(root); return false; } iobox_info = parseIoboxResponseObj(infoResponse); } cJSON* reasonResponse = cJSON_GetObjectItem(responseObj, "reason"); if (reasonResponse == nullptr) { // std::cerr << "Failed to get reason" << std::endl; cJSON_Delete(root); return true; } getReason = reasonResponse->valuestring; cJSON_Delete(root); return true; } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return false; } } std::string iobox_api::connectToIobox(const std::string& remote, int port, const std::string& username, const std::string& password) { // check ip is already in iobox_profiles bool is_exist = false; iobox_profile_t* item = nullptr; std::string aliasConnect = ""; bool is_onlyAuthen = false; std::lock_guard lock(this->_mutex); std::string ip = checkIpFromRemote(remote); if (ip == "") { this->_logger.LogError("iobox_api::connectToIobox. Failed to get ip from remote: ", remote, __FILE__, __LINE__); return aliasConnect; } std::cout << "IP address: " << ip << std::endl; try { if (!username.empty())_username = username; if (!password.empty())_password = password; if (port > 0)_port = port; for (auto& profile : this->iobox_profiles) { if (profile.iobox_info.ip_address == ip || profile.iobox_info.ip_public_address == ip) { is_exist = true; item = &profile; if (item->is_connected && item->is_anthenticated) { std::cout << "Ip is already connected and anthenticated: " << ip << std::endl; uint16_t idx = getIndexIoboxFromIp(ip); std::string aliasName = createAliasName(item->iobox_info.serial_number, item->iobox_info.hardware_version, item->iobox_info.firmware_version, idx); aliasConnect = createMulticastAlias(aliasName, item->iobox_info.model, item->iobox_info.serial_number, item->iobox_info.ip_public_address == "" ? item->iobox_info.ip_address : item->iobox_info.ip_public_address); return aliasConnect; } else if (item->is_connected && !item->is_anthenticated) { std::cout << "Ip is already connected but not anthenticated: " << ip << std::endl; // std::cout << "Please disconnect before reconnect" << std::endl; // return aliasConnect; is_onlyAuthen = true; } break; } } if (is_exist == false) { this->_logger.LogWarn("iobox_api::connectToIobox. Ip is not exist in list scan, now retry connect to this ip and append it to profile: ", ip, __FILE__, __LINE__); // return false; } // WSADATA wsaData; SOCKET sock = INVALID_SOCKET; struct sockaddr_in serverAddr; char recvBuf[1024]; int recvLen = 0; struct timeval tv; tv.tv_sec = 3000; tv.tv_usec = 0; if (is_onlyAuthen == false) { sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock == INVALID_SOCKET) { this->_logger.LogError("iobox_api::connectToIobox. Socket failed with error", std::to_string(WSAGetLastError()), __FILE__, __LINE__); // WSACleanup(); return aliasConnect; } serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = inet_addr(ip.c_str()); serverAddr.sin_port = htons(_port); if (connect(sock, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) { this->_logger.LogError("iobox_api::connectToIobox. Connect failed with error", std::to_string(WSAGetLastError()), __FILE__, __LINE__); closesocket(sock); // WSACleanup(); return aliasConnect; } } else { sock = item->sock_tcp; } setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)); std::string action = "auth"; //{"request":{"action":"auth","username":"","password":""}} std::string authMessage = "{\"request\":{\"action\":\"" + action + "\",\"username\":\"" + _username + "\",\"password\":\"" + _password + "\"}}"; int sendLen = send(sock, authMessage.c_str(), authMessage.length(), 0); if (sendLen == SOCKET_ERROR) { this->_logger.LogError("iobox_api::connectToIobox. Send failed with error", std::to_string(WSAGetLastError()), __FILE__, __LINE__); closesocket(sock); // WSACleanup(); return aliasConnect; } //{"response":{"action":"auth","result":"","reason":""}} iobox_info_t info; recvLen = recv(sock, recvBuf, sizeof(recvBuf) - 1, 0); if (recvLen > 0) { recvBuf[recvLen] = '\0'; std::cout << "Received message: " << recvBuf << std::endl; std::string response(recvBuf); std::string result; std::string reason; if (parseResponeCommon(response, action, result, reason, info)) { std::cout << "Parse response success" << std::endl; if (result == "success") { std::cout << "Authentication success" << std::endl; } else { this->_logger.LogError("iobox_api::connectToIobox. Authentication failed: ", reason, __FILE__, __LINE__); closesocket(sock); // WSACleanup(); return aliasConnect; } } else { this->_logger.LogError("iobox_api::connectToIobox. Parse failed: ", response, __FILE__, __LINE__); closesocket(sock); // WSACleanup(); return aliasConnect; } } else { this->_logger.LogError("iobox_api::connectToIobox. recv failed with error", std::to_string(WSAGetLastError()), __FILE__, __LINE__); closesocket(sock); // WSACleanup(); return aliasConnect; } // closesocket(sock); // WSACleanup(); if (item == nullptr) { if (info.ip_address == "") { this->_logger.LogError("iobox_api::connectToIobox. ", "Failed to get info from response", __FILE__, __LINE__); closesocket(sock); return aliasConnect; } if (info.ip_address != ip) { info.ip_public_address = ip; } save_info_iobox(info); for (auto& profile : this->iobox_profiles) { if (profile.iobox_info.ip_address == ip || profile.iobox_info.ip_public_address == ip) { item = &profile; break; } } } if (item == nullptr) return aliasConnect; uint16_t idx = getIndexIoboxFromIp(ip); std::string aliasName = createAliasName(item->iobox_info.serial_number, item->iobox_info.hardware_version, item->iobox_info.firmware_version, idx); aliasConnect = createMulticastAlias(aliasName, item->iobox_info.model, item->iobox_info.serial_number, item->iobox_info.ip_public_address == "" ? item->iobox_info.ip_address : item->iobox_info.ip_public_address); std::cout << "alias: " << aliasConnect << std::endl; item->sock_tcp = sock; item->is_connected = true; item->is_anthenticated = true; return aliasConnect; } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::connectToIobox. ", e.what(), __FILE__, __LINE__); return aliasConnect; } } bool iobox_api::connectToIoboxWithoutAuthen(const std::string& remote, int port) { // check ip is already in iobox_profiles bool is_exist = false; iobox_profile_t* item = nullptr; if (port > 0)_port = port; std::lock_guard lock(this->_mutex); std::string ip = checkIpFromRemote(remote); if (ip == "") { this->_logger.LogError("iobox_api::connectToIobox. Failed to get ip from remote: ", remote, __FILE__, __LINE__); return false; } try { for (auto& profile : this->iobox_profiles) { if (profile.iobox_info.ip_address == ip || profile.iobox_info.ip_public_address == ip) { is_exist = true; item = &profile; if (item->is_connected) { std::cout << "Ip is already connected: " << ip << std::endl; return true; } break; } } if (is_exist == false) { this->_logger.LogWarn("iobox_api::connectToIobox. Ip is not exist in list scan: ", ip, __FILE__, __LINE__); return false; } // WSADATA wsaData; SOCKET sock = INVALID_SOCKET; struct sockaddr_in serverAddr; char recvBuf[1024]; int recvLen = 0; struct timeval tv; tv.tv_sec = 3000; tv.tv_usec = 0; // Initialize Winsock // int result = WSAStartup(MAKEWORD(2, 2), &wsaData); // if (result != 0) { // std::cerr << "WSAStartup failed: " << result << std::endl; // return false; // } sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock == INVALID_SOCKET) { this->_logger.LogError("iobox_api::connectToIobox. Socket failed with error: ", std::to_string(WSAGetLastError()), __FILE__, __LINE__); // WSACleanup(); return false; } serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = inet_addr(ip.c_str()); serverAddr.sin_port = htons(_port); // // Bật keep-alive // int optval = 1; // int optlen = sizeof(optval); // if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char*)&optval, optlen) == SOCKET_ERROR) { // std::cerr << "setsockopt for keep-alive failed: " << WSAGetLastError() << std::endl; // closesocket(sock); // WSACleanup(); // return false; // } // // Optional: Adjust Keep-Alive parameters (requires platform-specific APIs). // tcp_keepalive kaSettings = { 1, 5000, 1000 }; // Enable, Idle time 5s, Interval 1s // DWORD bytesReturned; // WSAIoctl(sock, SIO_KEEPALIVE_VALS, &kaSettings, sizeof(kaSettings), NULL, 0, &bytesReturned, NULL, NULL); if (connect(sock, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) { this->_logger.LogError("iobox_api::connectToIobox. Connect failed with error: ", std::to_string(WSAGetLastError()), __FILE__, __LINE__); closesocket(sock); // WSACleanup(); return false; } // closesocket(sock); // WSACleanup(); item->sock_tcp = sock; item->is_connected = true; item->is_anthenticated = false; return true; } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::connectToIobox. ", e.what(), __FILE__, __LINE__); return false; } } bool iobox_api::disconnectToIoboxWithResetOutputs(const std::string& remote) { return disconnectToIobox(remote); //std::lock_guard lock(this->_mutex); //std::string ip = checkIpFromRemote(remote); //if (ip == "") { // this->_logger.LogError("iobox_api::disconnectToIobox. Failed to get ip from remote: ", remote, __FILE__, __LINE__); // return false; //} ////// Reset all outputs to 0 or off before disconnect ////setValue(remote, "DO1", "0"); ////setValue(remote, "DO2", "0"); ////setValue(remote, "DO3", "0"); ////setValue(remote, "DO4", "0"); //try { // // check ip is already in iobox_profiles // bool is_exist = false; // iobox_profile_t* item = nullptr; // for (auto& profile : this->iobox_profiles) { // if (profile.iobox_info.ip_address == ip || profile.iobox_info.ip_public_address == ip) { // item = &profile; // is_exist = true; // if (item->is_connected) { // std::cout << "Ip is already connected: " << ip << std::endl; // } // break; // } // } // if (is_exist == false) { // this->_logger.LogError("iobox_api::disconnectToIobox. Ip is not exist in list scan: ", ip, __FILE__, __LINE__); // return false; // } // item->counting_get = 0; // item->is_connected = false; // item->is_anthenticated = false; // closesocket(item->sock_tcp); // item->sock_tcp = INVALID_SOCKET; // // WSACleanup(); // std::cout << "Disconnected" << std::endl; // return true; //} //catch (const std::exception& e) { // this->_logger.LogFatal("iobox_api::disconnectToIobox. ", e.what(), __FILE__, __LINE__); // return false; //} } bool iobox_api::disconnectToIobox(const std::string& remote) { std::lock_guard lock(this->_mutex); std::string ip = checkIpFromRemote(remote); if (ip == "") { this->_logger.LogError("iobox_api::disconnectToIobox. Failed to get ip from remote: ", remote, __FILE__, __LINE__); return false; } try { // check ip is already in iobox_profiles bool is_exist = false; iobox_profile_t* item = nullptr; for (auto& profile : this->iobox_profiles) { if (profile.iobox_info.ip_address == ip || profile.iobox_info.ip_public_address == ip) { item = &profile; is_exist = true; if (item->is_connected) { std::cout << "Ip is already connected: " << ip << std::endl; } break; } } if (is_exist == false) { this->_logger.LogWarn("iobox_api::disconnectToIobox. Ip is not exist in list scan: ", ip, __FILE__, __LINE__); return false; } item->counting_get = 0; item->is_connected = false; item->is_anthenticated = false; closesocket(item->sock_tcp); item->sock_tcp = INVALID_SOCKET; // WSACleanup(); std::cout << "Disconnected" << std::endl; return true; } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::disconnectToIobox. ", e.what(), __FILE__, __LINE__); return false; } } bool iobox_api::setAuthenticationIobox(const std::string& remote, const std::string& username, const std::string& password) { std::lock_guard lock(this->_mutex); std::string ip = checkIpFromRemote(remote); if (ip == "") { this->_logger.LogError("iobox_api::setAuthenticationIobox. Failed to get ip from remote: ", remote, __FILE__, __LINE__); return false; } try { iobox_profile_t* profile = findProfileByIp(ip); if (profile == nullptr) { std::cerr << "IP address not found: " << ip << std::endl; return false; } if (!username.empty())_username = username; if (!password.empty())_password = password; std::string action = "change_auth"; //{"request": {"action":"change_auth","username":"","password":""}} std::string authMessage = "{\"request\":{\"action\":\"" + action + "\",\"username\":\"" + _username + "\",\"password\":\"" + _password + "\"}}"; if (!sendTcpMessage(ip, authMessage.c_str(), authMessage.length(), true)) { return false; } //{"response":{"action":"change_auth","result":"","reason":""}} char revBuf[256]; size_t responseLength = sizeof(revBuf); if (!receiveTcpMessage(ip, revBuf, responseLength)) { return false; } revBuf[responseLength] = '\0'; std::string response(revBuf); std::string result; std::string reason; iobox_info_t info; if (parseResponeCommon(response, action, result, reason, info)) { std::cout << "Parse response success" << std::endl; if (result == "success") { std::cout << "Change auth success" << std::endl; } else { this->_logger.LogError("iobox_api::setAuthenticationIobox. Change auth failed: ", reason, __FILE__, __LINE__); return false; } } else { this->_logger.LogError("iobox_api::setAuthenticationIobox. Parse response failed: ", response, __FILE__, __LINE__); return false; } return true; } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::setAuthenticationIobox. ", e.what(), __FILE__, __LINE__); return false; } } std::string iobox_api::generateToken(const std::string& remote) { std::lock_guard lock(_mutex); try { std::string ip = checkIpFromRemote(remote); if (ip == "") { this->_logger.LogError("iobox_api::generateToken. Failed to get ip from remote: ", remote, __FILE__, __LINE__); return ""; } iobox_profile_t* profile = findProfileByIp(ip); if (profile == nullptr) { this->_logger.LogError("iobox_api::generateToken. IP address not found: ", ip, __FILE__, __LINE__); return ""; } //may be change this later return profile->iobox_info.serial_number + profile->iobox_info.model; } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::generateToken. ", e.what(), __FILE__, __LINE__); return ""; } } bool iobox_api::resetAuthenticationIobox(const std::string& remote, const std::string& token) { std::lock_guard lock(this->_mutex); std::string ip = checkIpFromRemote(remote); if (ip == "") { this->_logger.LogError("iobox_api::resetAuthenticationIobox. Failed to get ip from remote: ", remote, __FILE__, __LINE__); return false; } try { bool needRemove = false; iobox_profile_t* profile = findProfileByIp(ip); if (profile == nullptr) { this->_logger.LogError("iobox_api::resetAuthenticationIobox. IP address not found: ", ip, __FILE__, __LINE__); // return false; // need temporary save iobox_info iobox_info_t info; info.ip_address = ip; save_info_iobox(info); profile = findProfileByIp(ip); needRemove = true; } if (connectToIoboxWithoutAuthen(ip, DEVICE_TCP_PORT) == false) { if (needRemove) remove_last_info_iobox(); return false; } //{"request": {"action":"factory_reset","token":""}} std::string action = "factory_reset"; std::string resetMessage = "{\"request\":{\"action\":\"" + action + "\",\"token\":\"" + token + "\"}}"; if (!sendTcpMessage(ip, resetMessage.c_str(), resetMessage.length(), false)) { if (profile->is_anthenticated == false) { disconnectToIobox(ip); if (needRemove) remove_last_info_iobox(); } return false; } char revBuf[256]; size_t responseLength = sizeof(revBuf); if (!receiveTcpMessage(ip, revBuf, responseLength)) { if (profile->is_anthenticated == false) { disconnectToIobox(ip); if (needRemove) remove_last_info_iobox(); } return false; } revBuf[responseLength] = '\0'; std::string response(revBuf); std::string result; std::string reason; iobox_info_t info; if (parseResponeCommon(response, action, result, reason, info)) { std::cout << "Parse response success" << std::endl; if (result == "success") { std::cout << "Factory reset success" << std::endl; if (profile->is_anthenticated == false) { disconnectToIobox(ip); if (needRemove) remove_last_info_iobox(); } return true; } else { this->_logger.LogError("iobox_api::resetAuthenticationIobox. Factory reset failed: ", reason, __FILE__, __LINE__); if (profile->is_anthenticated == false) { disconnectToIobox(ip); if (needRemove) remove_last_info_iobox(); } return false; } } else { this->_logger.LogError("iobox_api::resetAuthenticationIobox. Parse response failed: ", response, __FILE__, __LINE__); if (profile->is_anthenticated == false) { disconnectToIobox(ip); if (needRemove) remove_last_info_iobox(); } return false; } } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::resetAuthenticationIobox. ", e.what(), __FILE__, __LINE__); return false; } } bool iobox_api::resetIobox(const std::string& remote, const std::string& token) { std::lock_guard lock(this->_mutex); std::string ip = checkIpFromRemote(remote); if (ip == "") { this->_logger.LogError("iobox_api::resetIobox. Failed to get ip from remote: ", remote, __FILE__, __LINE__); return false; } try { bool needRemove = false; iobox_profile_t* profile = findProfileByIp(ip); if (profile == nullptr) { this->_logger.LogError("iobox_api::resetIobox. IP address not found: ", ip, __FILE__, __LINE__); // return false; // need temporary save iobox_info iobox_info_t info; info.ip_address = ip; save_info_iobox(info); profile = findProfileByIp(ip); needRemove = true; } if (connectToIoboxWithoutAuthen(ip, DEVICE_TCP_PORT) == false) { if (needRemove) remove_last_info_iobox(); return false; } //{"request": {"action":"restart","token":""}} std::string action = "restart"; std::string resetMessage = "{\"request\":{\"action\":\"" + action + "\",\"token\":\"" + token + "\"}}"; if (!sendTcpMessage(ip, resetMessage.c_str(), resetMessage.length(), false)) { if (profile->is_anthenticated == false) { disconnectToIobox(ip); if (needRemove) remove_last_info_iobox(); } return false; } char revBuf[256]; size_t responseLength = sizeof(revBuf); if (!receiveTcpMessage(ip, revBuf, responseLength)) { if (profile->is_anthenticated == false) { disconnectToIobox(ip); if (needRemove) remove_last_info_iobox(); } return false; } revBuf[responseLength] = '\0'; std::string response(revBuf); std::string result; std::string reason; iobox_info_t info; if (parseResponeCommon(response, action, result, reason, info)) { std::cout << "Parse response success" << std::endl; if (result == "success") { std::cout << "Restart success" << std::endl; } else { this->_logger.LogError("iobox_api::resetIobox. Restart failed: ", reason, __FILE__, __LINE__); if (profile->is_anthenticated == false) { disconnectToIobox(ip); if (needRemove) remove_last_info_iobox(); } return false; } } else { this->_logger.LogError("iobox_api::resetIobox. Parse response failed: ", response, __FILE__, __LINE__); if (profile->is_anthenticated == false) { disconnectToIobox(ip); if (needRemove) remove_last_info_iobox(); } return false; } disconnectToIobox(ip); if (needRemove) remove_last_info_iobox(); return true; } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::resetIobox. ", e.what(), __FILE__, __LINE__); return false; } } iobox_profile_t* iobox_api::findProfileByIp(const std::string& ip) { std::lock_guard lock(_mutex); try { iobox_profile_t* profile = nullptr; for (auto& item : this->iobox_profiles) { if (item.iobox_info.ip_address == ip || item.iobox_info.ip_public_address == ip) { profile = &item; } } return profile; } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::findProfileByIp. ", e.what(), __FILE__, __LINE__); return nullptr; } } bool iobox_api::performSetValue(const std::string& ip, const std::string& channelName, const std::string& value) { const std::string action = "setValue"; const std::string setValueMessage = "{\"request\":{\"action\":\"" + action + "\",\"channel\":\"" + channelName + "\",\"value\":\"" + value + "\"}}"; if (!sendTcpMessage(ip, setValueMessage.c_str(), setValueMessage.length(), true)) { this->_logger.LogError("iobox_api::performSetValue.", " Failed to send TCP message", __FILE__, __LINE__); return false; } constexpr size_t BUFFER_SIZE = 512; // Increased buffer size char revBuf[BUFFER_SIZE]; size_t responseLength = BUFFER_SIZE - 1; // Leave space for null terminator if (!receiveTcpMessage(ip, revBuf, responseLength)) { this->_logger.LogError("iobox_api::performSetValue. ", "Failed to receive TCP message", __FILE__, __LINE__); return false; } // Ensure null termination revBuf[responseLength] = '\0'; std::string response(revBuf); std::string result, reason; iobox_info_t info; if (!parseResponeCommon(response, action, result, reason, info)) { this->_logger.LogError("iobox_api::performSetValue. Failed to parse response:", response, __FILE__, __LINE__); return false; } if (result != "success") { //this->_logger.LogError("iobox_api::performSetValue. Set operation failed. Reason:", reason, __FILE__, __LINE__); return false; } return true; } bool iobox_api::performGetValue(const std::string& ip, const std::string& channelName, std::string& outValue) { const std::string action = "getValue"; const std::string getValueMessage = "{\"request\":{\"action\":\"" + action + "\",\"channel\":\"" + channelName + "\"}}"; if (!sendTcpMessage(ip, getValueMessage.c_str(), getValueMessage.length(), true)) { this->_logger.LogError("iobox_api::performGetValue.", " Failed to send TCP message", __FILE__, __LINE__); return false; } constexpr size_t BUFFER_SIZE = 512; // Increased buffer size char revBuf[BUFFER_SIZE]; size_t responseLength = BUFFER_SIZE - 1; // Leave space for null terminator if (!receiveTcpMessage(ip, revBuf, responseLength)) { this->_logger.LogError("iobox_api::performGetValue.", "Failed to receive TCP message", __FILE__, __LINE__); return false; } // Ensure null termination revBuf[responseLength] = '\0'; std::string response(revBuf); std::string result, reason; iobox_info_t info; if (!parseResponeCommon(response, action, result, reason, info)) { this->_logger.LogError("iobox_api::performGetValue. Failed to parse response:", response, __FILE__, __LINE__); return false; } outValue = result; if (result == "fail") { //this->_logger.LogError("iobox_api::performGetValue. Get operation failed. Result:", result, __FILE__, __LINE__); return false; } return true; } bool iobox_api::getValueDataStringIoboxFromChannelName(const std::string& remote, const std::string& channelName, std::string& outValue) { std::lock_guard lock(this->_mutex); // Clear output parameter outValue.clear(); // Validate input parameters if (remote.empty() || channelName.empty()) { this->_logger.LogError("iobox_api::getValueDataStringIoboxFromChannelName.", "Invalid parameters - remote or channelName is empty", __FILE__, __LINE__); return false; } std::string ip = checkIpFromRemote(remote); if (ip.empty()) { this->_logger.LogError("iobox_api::getValueDataStringIoboxFromChannelName. Failed to get ip from remote:", remote, __FILE__, __LINE__); return false; } // Check if we has connected to iobox if (!checkTcpConnectStatus(remote)) { disconnectToIobox(remote); // Ensure any previous connection is closed connectToIobox(remote, 0, "", ""); } if (!checkTcpConnectStatus(remote))return false; try { iobox_profile_t* profile = findProfileByIp(ip); if (profile == nullptr) { this->_logger.LogError("iobox_api::getValueDataStringIoboxFromChannelName. IP address not found:", ip, __FILE__, __LINE__); return false; } if (!performGetValue(ip, channelName, outValue)) { return false; } this->_logger.LogDebug("iobox_api::getValueDataStringIoboxFromChannelName. Successfully retrieved value for channel:", channelName, __FILE__, __LINE__); return true; } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::getValueDataStringIoboxFromChannelName. Exception:", e.what(), __FILE__, __LINE__); return false; } } bool iobox_api::setValueDataStringIoboxFromChannelName(const std::string& remote, const std::string& channelName, const std::string& value) { std::lock_guard lock(this->_mutex); // Validate input parameters if (remote.empty() || channelName.empty()) { this->_logger.LogError("iobox_api::setValueDataStringIoboxFromChannelName.", "Invalid parameters - remote or channelName is empty", __FILE__, __LINE__); return false; } std::string ip = checkIpFromRemote(remote); if (ip.empty()) { this->_logger.LogError("iobox_api::setValueDataStringIoboxFromChannelName. Failed to get ip from remote:", remote, __FILE__, __LINE__); return false; } // Check if we has connected to iobox if (!checkTcpConnectStatus(remote)) { disconnectToIobox(remote); // Ensure any previous connection is closed connectToIobox(remote, 0, "", ""); } if (!checkTcpConnectStatus(remote))return false; try { iobox_profile_t* profile = findProfileByIp(ip); if (profile == nullptr) { this->_logger.LogError("iobox_api::setValueDataStringIoboxFromChannelName. IP address not found:", ip, __FILE__, __LINE__); return false; } // Step 1: Set the value if (!performSetValue(ip, channelName, value)) { return false; } // Step 2: Read back and verify the value std::string readValue; if (!performGetValue(ip, channelName, readValue)) { return false; } // Step 3: Compare values if (readValue != value) { this->_logger.LogError("iobox_api::setValueDataStringIoboxFromChannelName. Value verification failed. Expected:", value, __FILE__, __LINE__); this->_logger.LogError("iobox_api::setValueDataStringIoboxFromChannelName. Value verification failed. Got:", readValue, __FILE__, __LINE__); return false; } this->_logger.LogDebug("iobox_api::setValueDataStringIoboxFromChannelName. Value set and verified successfully for channel:", channelName, __FILE__, __LINE__); return true; } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::setValueDataStringIoboxFromChannelName. Exception:", e.what(), __FILE__, __LINE__); return false; } } std::vector iobox_api::getDeviceChannelNames(const std::string& remote) { bool needRemove = false; std::vector channelNames; std::lock_guard lock(this->_mutex); std::string ip = checkIpFromRemote(remote); if (ip == "") { this->_logger.LogError("iobox_api::getDeviceChannelNames. Failed to get ip from remote:", remote, __FILE__, __LINE__); return channelNames; } try { iobox_profile_t* profile = findProfileByIp(ip); if (profile == nullptr) { this->_logger.LogError("iobox_api::getDeviceChannelNames. IP address not found:", ip, __FILE__, __LINE__); iobox_info_t info; info.ip_address = ip; save_info_iobox(info); profile = findProfileByIp(ip); needRemove = true; } if (connectToIoboxWithoutAuthen(ip, DEVICE_TCP_PORT) == false) { if (needRemove) remove_last_info_iobox(); return channelNames; } //{"request": {"action":"channelList"}} std::string action = "channelList"; std::string channelListMessage = "{\"request\":{\"action\":\"" + action + "\"}}"; if (!sendTcpMessage(ip, channelListMessage.c_str(), channelListMessage.length(), false)) { disconnectToIobox(ip); if (needRemove) remove_last_info_iobox(); return channelNames; } char revBuf[1024]; size_t responseLength = sizeof(revBuf); if (!receiveTcpMessage(ip, revBuf, responseLength)) { disconnectToIobox(ip); if (needRemove) remove_last_info_iobox(); return channelNames; } revBuf[responseLength] = '\0'; std::string response(revBuf); std::string result; std::string reason; iobox_info_t info; if (parseResponeCommon(response, action, result, reason, info)) { std::cout << "Parse response success" << std::endl; cJSON* root = cJSON_Parse(result.c_str()); if (root == nullptr) { this->_logger.LogError("iobox_api::getDeviceChannelNames. Failed to parse JSON:", result, __FILE__, __LINE__); disconnectToIobox(ip); if (needRemove) remove_last_info_iobox(); return channelNames; } if (root->type != cJSON_Array) { this->_logger.LogError("iobox_api::getDeviceChannelNames. Failed to parse JSON:", "Failed to get array", __FILE__, __LINE__); cJSON_Delete(root); disconnectToIobox(ip); if (needRemove) remove_last_info_iobox(); return channelNames; } cJSON* channelList = root; if (info.ip_address != ip) { info.ip_public_address = ip; } profile->iobox_info = info; int arraySize = cJSON_GetArraySize(channelList); for (int i = 0; i < arraySize; ++i) { cJSON* channel = cJSON_GetArrayItem(channelList, i); if (channel != nullptr && channel->type == cJSON_String) { uint16_t idx = getIndexIoboxFromIp(ip); std::string aliasName = createAliasName(info.serial_number, info.hardware_version, info.firmware_version, idx); std::string multicastAlias = createMulticastAlias(aliasName, info.model, info.serial_number, info.ip_public_address == "" ? info.ip_address : info.ip_public_address); std::string channelName = multicastAlias + "-" + channel->valuestring; channelNames.push_back(channelName); } } cJSON_Delete(root); // disconnectToIobox(ip); //not need remove return channelNames; } else { this->_logger.LogError("iobox_api::getDeviceChannelNames. Parse response failed:", response, __FILE__, __LINE__); //disconnectToIobox(ip); if (needRemove) remove_last_info_iobox(); return channelNames; } } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::getDeviceChannelNames.", e.what(), __FILE__, __LINE__); return channelNames; } } bool iobox_api::openRs232Port(const std::string& remote, int baudrate, int dataBits, int stopBits, int parityBits) { std::lock_guard lock(this->_mutex); std::string ip = checkIpFromRemote(remote); if (ip == "") { std::cerr << "Failed to get ip from remote: " << remote << std::endl; return false; } try { iobox_profile_t* profile = findProfileByIp(ip); if (profile == nullptr) { std::cerr << "IP address not found: " << ip << std::endl; return false; } //{"request":{"action":"openRs232","dataBits":,"stopBit":,"parityBit":"","baud": }} std::string action = "openRs232"; std::string parityStr = "none"; if (parityBits == 1) { parityStr = "odd"; } else if (parityBits == 2) { parityStr = "even"; } std::string openRs232Message = "{\"request\":{\"action\":\"" + action + "\",\"dataBits\":" + std::to_string(dataBits) + ",\"stopBit\":" + std::to_string(stopBits) + ",\"parityBit\":\"" + parityStr + "\",\"baud\":" + std::to_string(baudrate) + "}}"; if (!sendTcpMessage(ip, openRs232Message.c_str(), openRs232Message.length(), true)) { return false; } char revBuf[256]; size_t responseLength = sizeof(revBuf); if (!receiveTcpMessage(ip, revBuf, responseLength)) { return false; } revBuf[responseLength] = '\0'; std::string response(revBuf); std::string result; std::string reason; iobox_info_t info; if (parseResponeCommon(response, action, result, reason, info)) { std::cout << "Parse response success" << std::endl; if (result == "success") { std::cout << "Open RS232 port success" << std::endl; } else { std::cerr << "Open RS232 port failed" << std::endl; std::cerr << "Reason: " << reason << std::endl; return false; } } else { std::cerr << "Parse response failed" << std::endl; return false; } return true; } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return false; } } bool iobox_api::closeRs232Port(const std::string& remote) { std::lock_guard lock(this->_mutex); std::string ip = checkIpFromRemote(remote); if (ip == "") { std::cerr << "Failed to get ip from remote: " << remote << std::endl; return false; } try { iobox_profile_t* profile = findProfileByIp(ip); if (profile == nullptr) { std::cerr << "IP address not found: " << ip << std::endl; return false; } //{"request":{"action":"closeRs232"}} std::string action = "closeRs232"; std::string openRs232Message = "{\"request\":{\"action\":\"" + action + "\"}}"; if (!sendTcpMessage(ip, openRs232Message.c_str(), openRs232Message.length(), true)) { return false; } char revBuf[256]; size_t responseLength = sizeof(revBuf); if (!receiveTcpMessage(ip, revBuf, responseLength)) { return false; } revBuf[responseLength] = '\0'; std::string response(revBuf); std::string result; std::string reason; iobox_info_t info; if (parseResponeCommon(response, action, result, reason, info)) { std::cout << "Parse response success" << std::endl; if (result == "success") { std::cout << "Close RS232 port success" << std::endl; } else { std::cerr << "Close RS232 port failed" << std::endl; std::cerr << "Reason: " << reason << std::endl; return false; } } else { std::cerr << "Parse response failed" << std::endl; return false; } return true; } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return false; } } bool iobox_api::writeRs232Port(const std::string& remote, const std::string& data, int timeout_ms) { std::lock_guard lock(this->_mutex); std::string ip = checkIpFromRemote(remote); if (ip == "") { std::cerr << "Failed to get ip from remote: " << remote << std::endl; return false; } try { iobox_profile_t* profile = findProfileByIp(ip); if (profile == nullptr) { std::cerr << "IP address not found: " << ip << std::endl; return false; } //{"request":{"action":"writeRs232","dataType":"","data":""}} std::string action = "writeRs232"; std::string hexStringData = stringToHexString(data); size_t maxChunkSize = 64 * 2; // 64 bytes in hex size_t dataSize = hexStringData.size(); size_t offset = 0; auto start = std::chrono::steady_clock::now(); while (offset < dataSize) { if (std::chrono::steady_clock::now() - start > std::chrono::milliseconds(timeout_ms)) { std::cerr << "Write RS232 port timeout" << std::endl; return false; } size_t chunkSize = min(maxChunkSize, dataSize - offset); std::string chunk = hexStringData.substr(offset, chunkSize); std::string writeRs232Message = "{\"request\":{\"action\":\"" + action + "\",\"dataType\":\"hexstring\",\"data\":\"" + chunk + "\"}}"; if (!sendTcpMessage(ip, writeRs232Message.c_str(), writeRs232Message.length(), true)) { return false; } char revBuf[256]; size_t responseLength = sizeof(revBuf); if (!receiveTcpMessage(ip, revBuf, responseLength)) { return false; } revBuf[responseLength] = '\0'; std::string response(revBuf); std::string result; std::string reason; iobox_info_t info; if (parseResponeCommon(response, action, result, reason, info)) { std::cout << "Parse response success" << std::endl; if (result == "success") { std::cout << "Write RS232 port success" << std::endl; } else { std::cerr << "Write RS232 port failed" << std::endl; std::cerr << "Reason: " << reason << std::endl; return false; } } else { std::cerr << "Parse response failed" << std::endl; return false; } offset += chunkSize; } return true; } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return false; } } std::string iobox_api::readRs232Port(const std::string& remote, const std::string& terminatorString, int lenExpect, int timeout_ms) { if (terminatorString == "" && lenExpect == 0) { std::cerr << "terminatorString and lenExpect cannot be empty at the same time" << std::endl; return ""; } std::lock_guard lock(this->_mutex); std::string ip = checkIpFromRemote(remote); if (ip == "") { std::cerr << "Failed to get ip from remote: " << remote << std::endl; return ""; } try { iobox_profile_t* profile = findProfileByIp(ip); if (profile == nullptr) { std::cerr << "IP address not found: " << ip << std::endl; return ""; } //{"request":{"action":"readRs232","terminatorType":"","timeout": }} std::string action = "readRs232"; if (terminatorString != "") { std::string readRs232Message = "{\"request\":{\"action\":\"" + action + "\",\"terminatorType\":\"characters\",\"terminator\":\"" + terminatorString + "\",\"timeout\":" + std::to_string(timeout_ms) + "}}"; if (!sendTcpMessage(ip, readRs232Message.c_str(), readRs232Message.length(), true)) { return ""; } } else if (lenExpect > 0) { std::string readRs232Message = "{\"request\":{\"action\":\"" + action + "\",\"terminatorType\":\"len\",\"terminator\":\"" + std::to_string(lenExpect) + "\",\"timeout\":" + std::to_string(timeout_ms) + "}}"; if (!sendTcpMessage(ip, readRs232Message.c_str(), readRs232Message.length(), true)) { return ""; } } char revBuf[1024]; size_t responseLength = sizeof(revBuf); if (!receiveTcpMessage(ip, revBuf, responseLength, timeout_ms)) { return ""; } revBuf[responseLength] = '\0'; std::string response(revBuf); std::string result; std::string reason; iobox_info_t info; if (parseResponeCommon(response, action, result, reason, info)) { std::cout << "Parse response success" << std::endl; //may be revBuf is normal buffer or cstring, so need to pass responseLength if (result == "") { std::cerr << "Read RS232 port failed" << std::endl; std::cerr << "Reason: " << reason << std::endl; return ""; } else { if (reason == "string") { return result; } else if (reason == "hexstring") { std::string hexString = result; std::string data; for (size_t i = 0; i < hexString.length(); i += 2) { std::string byteString = hexString.substr(i, 2); char byte = (char)strtol(byteString.c_str(), nullptr, 16); data.push_back(byte); } return data; } else { return ""; } } return result; } else { std::cerr << "Parse response failed" << std::endl; return ""; } } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return ""; } } std::string ota_hash256OfFile(const std::string& filename) { std::ifstream file(filename, std::ios::binary); if (!file.is_open()) { std::cerr << "Failed to open file: " << filename << std::endl; return ""; } std::vector buffer(1024); std::vector hashBuffer(32); mbedtls_sha256_context ctx; mbedtls_sha256_init(&ctx); mbedtls_sha256_starts(&ctx, 0); while (!file.eof()) { file.read((char*)buffer.data(), buffer.size()); size_t bytesRead = file.gcount(); if (bytesRead > 0) { mbedtls_sha256_update(&ctx, buffer.data(), bytesRead); } } mbedtls_sha256_finish(&ctx, hashBuffer.data()); mbedtls_sha256_free(&ctx); file.close(); std::stringstream ss; for (uint8_t byte : hashBuffer) { ss << std::hex << std::setw(2) << std::setfill('0') << (int)byte; } return ss.str(); } bool iobox_api::otaFirmwareDevice(const std::string& remote, const std::string& filename, const std::string& type) { if (type != "esp" && type != "mcu") { this->_logger.LogFatal("iobox_api::otaFirmwareDevice.", "OTA type must be esp or mcu", __FILE__, __LINE__); return false; } std::lock_guard lock(this->_mutex); //check file name valid if (filename == "") { this->_logger.LogFatal("iobox_api::otaFirmwareDevice.", "Filename cannot be empty", __FILE__, __LINE__); return false; } //get len of file std::ifstream file(filename, std::ios::binary | std::ios::ate); if (!file.is_open()) { this->_logger.LogFatal("iobox_api::otaFirmwareDevice. Failed to open file:", filename, __FILE__, __LINE__); return false; } size_t lenBytes = file.tellg(); file.close(); if (lenBytes == 0) { this->_logger.LogFatal("iobox_api::otaFirmwareDevice. File is empty:", filename, __FILE__, __LINE__); return false; } std::cout << "File size: " << lenBytes << " bytes" << std::endl; std::string hash256 = ota_hash256OfFile(filename); if (hash256 == "") { this->_logger.LogFatal("iobox_api::otaFirmwareDevice. Failed to calculate hash of file:", filename, __FILE__, __LINE__); return false; } std::cout << "Hash: " << hash256 << std::endl; std::string ip = checkIpFromRemote(remote); if (ip == "") { this->_logger.LogFatal("iobox_api::otaFirmwareDevice. Failed to get ip from remote:", remote, __FILE__, __LINE__); return false; } try { iobox_profile_t* profile = findProfileByIp(ip); if (profile == nullptr) { this->_logger.LogError("iobox_api::otaFirmwareDevice. IP address not found:", ip, __FILE__, __LINE__); return false; } //{"request":{"action":"ota_start","otaType":","hash":""}} std::string action = "ota_start"; std::string otaStartMessage = "{\"request\":{\"action\":\"" + action + "\",\"otaType\":\"" + type + "\",\"otaTotalLen\":" + std::to_string(lenBytes) + ",\"hash\":\"" + hash256 + "\"}}"; if (!sendTcpMessage(ip, otaStartMessage.c_str(), otaStartMessage.length(), true)) { return false; } char revBuf[256]; size_t responseLength = sizeof(revBuf); if (!receiveTcpMessage(ip, revBuf, responseLength, 10000)) { return false; } revBuf[responseLength] = '\0'; std::string response(revBuf); std::string result; std::string reason; iobox_info_t info; if (parseResponeCommon(response, action, result, reason, info)) { std::cout << "Parse response success" << std::endl; if (result == "success") { std::cout << "OTA start success" << std::endl; } else { this->_logger.LogError("iobox_api::otaFirmwareDevice. OTA start failed:", result, __FILE__, __LINE__); return false; } } else { this->_logger.LogError("iobox_api::otaFirmwareDevice. Parse response failed:", response, __FILE__, __LINE__); return false; } //{"request":{"action":"ota_running","otaDataPackage":"","otaCurrentLen": }} std::ifstream otaFile(filename, std::ios::binary); if (!otaFile.is_open()) { this->_logger.LogError("iobox_api::otaFirmwareDevice. Failed to open file :", filename, __FILE__, __LINE__); return false; } size_t maxChunkSize = 256; std::vector buffer(maxChunkSize); size_t totalBytesSent = 0; while (!otaFile.eof()) { otaFile.read(buffer.data(), maxChunkSize); size_t bytesRead = otaFile.gcount(); std::cout << "Read " << bytesRead << " bytes" << std::endl; if (bytesRead > 0) { std::string hexStringData = stringToHexString(std::string(buffer.data(), bytesRead)); std::string otaRunningMessage = "{\"request\":{\"action\":\"ota_running\",\"otaDataPackage\":\"" + hexStringData + "\",\"otaCurrentLen\":" + std::to_string(totalBytesSent + bytesRead) + "}}"; if (!sendTcpMessage(ip, otaRunningMessage.c_str(), otaRunningMessage.length(), true)) { otaFile.close(); return false; } char revBuf[256]; size_t responseLength = sizeof(revBuf); if (!receiveTcpMessage(ip, revBuf, responseLength)) { otaFile.close(); return false; } revBuf[responseLength] = '\0'; std::string response(revBuf); std::string result; std::string reason; iobox_info_t info; if (!parseResponeCommon(response, "ota_running", result, reason, info) || result != "success") { this->_logger.LogError("iobox_api::otaFirmwareDevice. OTA running failed :", result, __FILE__, __LINE__); otaFile.close(); return false; } totalBytesSent += bytesRead; std::cout << "TotalBytesSent " << totalBytesSent << " bytes" << std::endl; } } otaFile.close(); //{"request":{"action":"ota_end"}} std::string otaEndMessage = "{\"request\":{\"action\":\"ota_end\"}}"; if (!sendTcpMessage(ip, otaEndMessage.c_str(), otaEndMessage.length(), true)) { return false; } char revBufEnd[256]; size_t responseLengthEnd = sizeof(revBufEnd); if (!receiveTcpMessage(ip, revBufEnd, responseLengthEnd, 22000)) { return false; } revBufEnd[responseLengthEnd] = '\0'; std::string responseEnd(revBufEnd); std::string resultEnd; std::string reasonEnd; iobox_info_t infoEnd; if (parseResponeCommon(responseEnd, "ota_end", resultEnd, reasonEnd, infoEnd)) { std::cout << "Parse response success" << std::endl; if (resultEnd == "success") { std::cout << "OTA end success" << std::endl; } else { this->_logger.LogError("iobox_api::otaFirmwareDevice. OTA end failed :", reasonEnd, __FILE__, __LINE__); return false; } } else { this->_logger.LogError("iobox_api::otaFirmwareDevice. Parse response failed :", responseEnd, __FILE__, __LINE__); return false; } std::cout << "OTA update completed successfully" << std::endl; return true; } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::otaFirmwareDevice.", e.what(), __FILE__, __LINE__); return false; } } bool iobox_api::checkTcpConnectStatus(const std::string& remote) { try { std::lock_guard lock(this->_mutex); std::string ip = checkIpFromRemote(remote); if (ip == "") { this->_logger.LogWarn("iobox_api::checkTcpConnectStatus. Failed to get ip from remote:", remote, __FILE__, __LINE__); return false; } iobox_profile_t* profile = findProfileByIp(ip); if (profile == nullptr) { this->_logger.LogWarn("iobox_api::checkTcpConnectStatus. IP address not found:", ip, __FILE__, __LINE__); return false; } if (profile->sock_tcp == INVALID_SOCKET) { this->_logger.LogWarn("iobox_api::checkTcpConnectStatus.", "Socket is invalid", __FILE__, __LINE__); return false; } std::string action = "ping"; std::string mid = std::to_string(rand()); std::string pingMessage = "{\"request\":{\"action\":\"" + action + "\",\"mid\":\"" + mid + "\"}}"; if (!sendTcpMessage(ip, pingMessage.c_str(), pingMessage.length(), false)) { disconnectToIobox(ip); this->_logger.LogWarn("iobox_api::checkTcpConnectStatus.", "Please retry connect", __FILE__, __LINE__); return false; } char revBuf[256]; size_t responseLength = sizeof(revBuf); if (!receiveTcpMessage(ip, revBuf, responseLength)) { disconnectToIobox(ip); this->_logger.LogWarn("iobox_api::checkTcpConnectStatus.", "Please retry connect", __FILE__, __LINE__); return false; } revBuf[responseLength] = '\0'; std::string response(revBuf); std::string result; std::string reason; iobox_info_t info; if (parseResponeCommon(response, action, result, reason, info)) { std::cout << "Parse response success" << std::endl; if (result == mid) { std::cout << "Ping success" << std::endl; return true; } else { this->_logger.LogError("iobox_api::checkTcpConnectStatus. Ping failed:", reason, __FILE__, __LINE__); // disconnectToIobox(ip); std::cout << "Please retry connect" << std::endl; return false; } } else { this->_logger.LogError("iobox_api::checkTcpConnectStatus. Parse response failed:", response, __FILE__, __LINE__); // disconnectToIobox(ip); std::cout << "Please retry connect" << std::endl; return false; } return true; } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::checkTcpConnectStatus.", e.what(), __FILE__, __LINE__); return false; } } std::vector iobox_api::advancedConnectToIobox(const std::string& remote, int port, const std::string& macAddress, const std::string& username, const std::string& password) { std::lock_guard lock(this->_mutex); std::vector result; std::string macAddressInUppercase = toUpperCase(macAddress); bool needToScan = false; // We will scan for list of SN -> IP (store in map) // 1. Check if we do need to scan for IP std::string ip = remote; if (!ip.empty()) { ip = checkIpFromRemote(remote); } if ((ip == "") || (ip.empty())) { // we need to check if any mac address is valid if (macAddress.empty() || (macAddress == "")) { this->_logger.LogError("iobox_api::advancedConnectToIobox. Failed to get ip from remote:", remote, __FILE__, __LINE__); return result; } else { // we check if we can find ip address from mac address from map otherwise we need to scan if (!macToIpMap.empty()) { auto it = macToIpMap.find(macAddressInUppercase); if (it != macToIpMap.end()) { ip = it->second; } else { needToScan = true; // we need to scan for list of SN -> IP (store in map) } } else { needToScan = true; // we need to scan for list of SN -> IP (store in map) } } } //2. We will need to find valid IP address std::string validIp = ip; if (needToScan) { // 2.1 We will use multcast to find valid IP address std::vector multiCastResults = scanNetworkDevicesMulticast(5); if (multiCastResults.empty()) { // We will manual scan for IP address std::vector uniCastResults = scanNetworkDevicesManually(5); if (uniCastResults.empty()) { this->_logger.LogError("iobox_api::advancedConnectToIobox. ", "Failed to find any iobox devices", __FILE__, __LINE__); return result; } } if (macToIpMap.empty()) { this->_logger.LogError("iobox_api::advancedConnectToIobox. ", "Failed to find any iobox devices", __FILE__, __LINE__); return result; } // 2.2 Find valid IP address from mac auto it = macToIpMap.find(macAddressInUppercase); if (it != macToIpMap.end()) { validIp = it->second; } else { this->_logger.LogError("iobox_api::advancedConnectToIobox. ", "MAC address not found.", __FILE__, __LINE__); return result; } } // 3. We will connect to iobox std::string deviceInfo = connectToIobox(validIp, port, username, password); if (deviceInfo.empty()) { // We try a second time to rescan // 3.1 We will use multcast to find valid IP address std::vector multiCastResults = scanNetworkDevicesMulticast(5); if (multiCastResults.empty()) { // We will manual scan for IP address std::vector uniCastResults = scanNetworkDevicesManually(5); if (uniCastResults.empty()) { this->_logger.LogError("iobox_api::advancedConnectToIobox. ", "Failed to find any iobox devices", __FILE__, __LINE__); return result; } } if (macToIpMap.empty()) { this->_logger.LogError("iobox_api::advancedConnectToIobox. ", "Failed to find any iobox devices", __FILE__, __LINE__); return result; } // 3.2 Find valid IP address from mac auto it = macToIpMap.find(macAddressInUppercase); if (it != macToIpMap.end()) { validIp = it->second; } else { this->_logger.LogError("iobox_api::advancedConnectToIobox. ", "MAC address not found.", __FILE__, __LINE__); return result; } deviceInfo = connectToIobox(validIp, port, username, password); if (deviceInfo.empty()) { this->_logger.LogError("iobox_api::advancedConnectToIobox. ", "Failed to connect to iobox", __FILE__, __LINE__); return result; } } // 4. Get device info std::vector deviceInfos; // 4.1 Search for device infos from ipToDeviceMap auto it = ipToDeviceMap.find(validIp); if (it != ipToDeviceMap.end()) { deviceInfos = it->second; } if (!deviceInfos.empty()) { if (!macAddress.empty()) { std::string firstInfoRecord = deviceInfos[0]; // Split the record into parts std::vector parts; std::stringstream ss(firstInfoRecord); std::string segment; while (std::getline(ss, segment, '-')) { parts.push_back(segment); } // Validate structure if (parts.size() >= 4) { std::string macAddressFromRecord = parts[2]; // 3rd element: MacAddress if (toUpperCase(macAddressFromRecord) == macAddressInUppercase) { result = deviceInfos; return result; } } } else { result = deviceInfos; return result; } } // 4.2 If not found, we will get device info from iobox result = getDeviceChannelNames(validIp); if (!result.empty()) { if (ipToDeviceMap.find(validIp) == ipToDeviceMap.end()) { ipToDeviceMap[validIp] = result; } } return result; } std::vector iobox_api::advancedScan(int timeout) { std::lock_guard lock(this->_mutex); std::vector result; // 1. We will use multcast to find valid IP address std::vector multiCastResults = scanNetworkDevicesMulticast(timeout); if (multiCastResults.empty()) { // We will manual scan for IP address std::vector uniCastResults = scanNetworkDevicesManually(timeout); } // 2. By now we do have ip address and mac map if (!macToIpMap.empty()) { // we go throught ip address found this the list by checking each map element for (const auto& pair : macToIpMap) { std::string macAddress = toUpperCase(pair.first); std::string ipAddress = pair.second; std::vector deviceInfos; auto it = ipToDeviceMap.find(ipAddress); if (it != ipToDeviceMap.end()) { deviceInfos = it->second; } if (!deviceInfos.empty()) { for (auto& deviceInfo : deviceInfos) { result.push_back(deviceInfo); } } } } else { this->_logger.LogError("iobox_api::advancedScan. ", "Failed to find any iobox devices", __FILE__, __LINE__); } return result; } bool iobox_api::setValue(const std::string& remote, const std::string& channelName, const std::string& value) { // Validate inputs if (remote.empty() || channelName.empty()) { this->_logger.LogError("iobox_api::setValue. ", "Invalid parameters - remote or channelName is empty", __FILE__, __LINE__); return false; } try { auto startTime = std::chrono::steady_clock::now(); auto timeoutDuration = std::chrono::milliseconds(IoboxConfig::TOTAL_TIMEOUT_MS); for (int attempt = 1; attempt <= IoboxConfig::MAX_RETRIES; ++attempt) { // Check if we've exceeded the total timeout auto elapsed = std::chrono::steady_clock::now() - startTime; if (elapsed >= timeoutDuration) { this->_logger.LogError("iobox_api::setValue. Total timeout exceeded after", std::to_string(std::chrono::duration_cast(elapsed).count()), __FILE__, __LINE__); break; } bool result = setValueDataStringIoboxFromChannelName(remote, channelName, value); if (result) { if (attempt > 1) { this->_logger.LogDebug("iobox_api::setValue. Succeeded on attempt", std::to_string(attempt), __FILE__, __LINE__); } return true; } // Log retry attempt (but not on first failure to avoid spam) if (attempt == 2) { this->_logger.LogWarn("iobox_api::setValue. First attempt failed, starting retries for channel:", channelName, __FILE__, __LINE__); } else if (attempt % 10 == 0) { this->_logger.LogWarn("iobox_api::setValue. Attempt", std::to_string(attempt), __FILE__, __LINE__); } // Don't sleep on the last attempt if (attempt < IoboxConfig::MAX_RETRIES) { std::this_thread::sleep_for(std::chrono::milliseconds(IoboxConfig::RETRY_DELAY_MS)); } } this->_logger.LogError("iobox_api::setValue. All retry attempts failed for channel after retries:", std::to_string(IoboxConfig::MAX_RETRIES), __FILE__, __LINE__); return false; } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::setValue. Exception:", e.what(), __FILE__, __LINE__); return false; } catch (...) { this->_logger.LogFatal("iobox_api::setValue.", "Unknown exception occurred", __FILE__, __LINE__); return false; } } bool iobox_api::getValue(const std::string& remote, const std::string& channelName, std::string& outValue) { // Clear output parameter outValue.clear(); // Validate inputs if (remote.empty() || channelName.empty()) { this->_logger.LogError("iobox_api::getValue. Invalid parameters - ", "remote or channelName is empty", __FILE__, __LINE__); return false; } try { auto startTime = std::chrono::steady_clock::now(); auto timeoutDuration = std::chrono::milliseconds(IoboxConfig::TOTAL_TIMEOUT_MS); for (int attempt = 1; attempt <= IoboxConfig::MAX_RETRIES; ++attempt) { // Check if we've exceeded the total timeout auto elapsed = std::chrono::steady_clock::now() - startTime; if (elapsed >= timeoutDuration) { this->_logger.LogError("iobox_api::getValue. Total timeout exceeded after", std::to_string(std::chrono::duration_cast(elapsed).count()), __FILE__, __LINE__); break; } // Use the improved version based on which option you chose earlier std::string tempValue; bool result = false; // Option 1: If using the output parameter version result = getValueDataStringIoboxFromChannelName(remote, channelName, tempValue); // Option 2: If using the std::optional version (uncomment if you chose this) /* auto optionalResult = getValueDataStringIoboxFromChannelName(remote, channelName); if (optionalResult.has_value()) { tempValue = *optionalResult; result = true; } */ // Option 3: If using the IoboxResult version (uncomment if you chose this) /* auto resultEx = getValueDataStringIoboxFromChannelNameEx(remote, channelName); if (resultEx.isSuccess()) { tempValue = resultEx.value; result = true; } */ if (result && !tempValue.empty()) { outValue = tempValue; if (attempt > 1) { this->_logger.LogDebug("iobox_api::getValue. Succeeded, value: ", outValue, __FILE__, __LINE__); } return true; } // Log retry attempt (but not on first failure to avoid spam) if (attempt == 2) { this->_logger.LogWarn("iobox_api::getValue. First attempt failed, starting retries for channel:", channelName, __FILE__, __LINE__); } else if (attempt % 10 == 0) { this->_logger.LogWarn("iobox_api::getValue. Attempt", std::to_string(attempt), __FILE__, __LINE__); } // Don't sleep on the last attempt if (attempt < IoboxConfig::MAX_RETRIES) { std::this_thread::sleep_for(std::chrono::milliseconds(IoboxConfig::RETRY_DELAY_MS)); } } this->_logger.LogError("iobox_api::getValue. All retry attempts failed for channel:", std::to_string(IoboxConfig::MAX_RETRIES), __FILE__, __LINE__); return false; } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::getValue. Exception:", e.what(), __FILE__, __LINE__); return false; } catch (...) { this->_logger.LogFatal("iobox_api::getValue.", "Unknown exception occurred", __FILE__, __LINE__); return false; } } bool iobox_api::toggleIobox(const std::string& remote, const std::string& channelName, int timeOut, bool revertFlag, bool resetFlag, bool asyncMode) { // Validate inputs if (timeOut < 0) { this->_logger.LogError("iobox_api::toggleIobox. Invalid timeout value", std::to_string(timeOut), __FILE__, __LINE__); return false; } if (remote.empty() || channelName.empty()) { this->_logger.LogError("iobox_api::toggleIobox. Invalid parameters", "", __FILE__, __LINE__); return false; } std::string key = remote + ":" + channelName; std::shared_ptr toggleInfo; bool isNewOperation = false; // Critical section for map access { std::lock_guard mapLock(toggleMapMutex); auto it = activeToggles.find(key); if (it != activeToggles.end()) { // Existing operation found for this channel if (!resetFlag) { this->_logger.LogWarn("iobox_api::toggleIobox. Toggle already active", channelName, __FILE__, __LINE__); return false; // Reject new call when resetFlag is false } // Reset the timeout for existing operation toggleInfo = it->second; { std::lock_guard infoLock(toggleInfo->mtx); toggleInfo->endTime = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeOut); toggleInfo->cancelled = false; // Reset cancellation flag toggleInfo->cv.notify_one(); } this->_logger.LogDebug("iobox_api::toggleIobox. Reset timeout", channelName, __FILE__, __LINE__); return true; // Reset successful } else { // Create new operation for this channel toggleInfo = std::make_shared(); toggleInfo->endTime = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeOut); toggleInfo->revertFlag = revertFlag; toggleInfo->active = true; toggleInfo->cancelled = false; // Get original state before starting toggle using retry logic bool getResult = getValue(remote, channelName, toggleInfo->originalState); if (!getResult || toggleInfo->originalState.empty()) { this->_logger.LogError("iobox_api::toggleIobox. Failed to read current state", channelName, __FILE__, __LINE__); return false; // Failed to read current state } activeToggles[key] = toggleInfo; isNewOperation = true; this->_logger.LogDebug("iobox_api::toggleIobox. Started new toggle operation", channelName, __FILE__, __LINE__); } } // Only new operations continue from here if (!isNewOperation) return true; // For async mode, start operation in separate thread and return immediately if (asyncMode) { std::thread asyncWorker([this, remote, channelName, toggleInfo, key]() { this->executeToggleOperation(remote, channelName, toggleInfo, key); }); asyncWorker.detach(); // Detach thread to run independently this->_logger.LogDebug("iobox_api::toggleIobox. Started async toggle", channelName, __FILE__, __LINE__); return true; // Return immediately for async mode } // For sync mode, execute operation in current thread return executeToggleOperation(remote, channelName, toggleInfo, key); } // Enhanced helper function with comprehensive verification bool iobox_api::executeToggleOperation(const std::string& remote, const std::string& channelName, std::shared_ptr toggleInfo, const std::string& key) { // RAII cleanup helper auto cleanup = [this, &key, &channelName]() { std::lock_guard mapLock(toggleMapMutex); activeToggles.erase(key); this->_logger.LogDebug("iobox_api::executeToggleOperation. Cleaned up toggle", channelName, __FILE__, __LINE__); }; try { // Define ON/OFF states (make these configurable if needed) const std::string onState = "1"; const std::string offState = "0"; // Determine toggle states - create complete toggle cycle regardless of current state std::string firstState, secondState; if (toggleInfo->revertFlag) { // Revert mode: go to OFF first, then back to ON firstState = offState; // Always go to OFF first secondState = onState; // Then revert to ON this->_logger.LogDebug("iobox_api::executeToggleOperation. Revert mode OFF-ON", channelName, __FILE__, __LINE__); } else { // Normal mode: go to ON first, then to OFF firstState = onState; // Always go to ON first secondState = offState; // Then go to OFF (complete the toggle cycle) this->_logger.LogDebug("iobox_api::executeToggleOperation. Normal mode ON-OFF", channelName, __FILE__, __LINE__); } // Phase 1: Set the initial toggle state with verification this->_logger.LogDebug("iobox_api::executeToggleOperation. Setting initial state", firstState, __FILE__, __LINE__); if (!setValue(remote, channelName, firstState)) { this->_logger.LogError("iobox_api::executeToggleOperation. Failed to set initial state", channelName, __FILE__, __LINE__); cleanup(); return false; } // Verify the initial state was set correctly using getValue (which includes retries) std::string verifyValue; if (!getValue(remote, channelName, verifyValue)) { this->_logger.LogError("iobox_api::executeToggleOperation. Failed to verify initial state", channelName, __FILE__, __LINE__); cleanup(); return false; } if (verifyValue != firstState) { this->_logger.LogError("iobox_api::executeToggleOperation. Initial state verification failed", firstState, __FILE__, __LINE__); cleanup(); return false; } this->_logger.LogDebug("iobox_api::executeToggleOperation. Initial state verified", channelName, __FILE__, __LINE__); // Phase 2: Wait for timeout with possible resets from other threads auto waitStartTime = std::chrono::steady_clock::now(); { std::unique_lock lock(toggleInfo->mtx); while (toggleInfo->active && !toggleInfo->cancelled && std::chrono::steady_clock::now() < toggleInfo->endTime) { auto waitResult = toggleInfo->cv.wait_until(lock, toggleInfo->endTime); // Check if we were cancelled if (toggleInfo->cancelled) { this->_logger.LogDebug("iobox_api::executeToggleOperation. Toggle cancelled", channelName, __FILE__, __LINE__); cleanup(); return false; } // Log if timeout was extended if (waitResult == std::cv_status::no_timeout) { this->_logger.LogDebug("iobox_api::executeToggleOperation. Timeout extended", channelName, __FILE__, __LINE__); } } } auto waitDuration = std::chrono::steady_clock::now() - waitStartTime; auto waitMs = std::chrono::duration_cast(waitDuration).count(); this->_logger.LogDebug("iobox_api::executeToggleOperation. Wait completed", channelName, __FILE__, __LINE__); // Phase 3: Set final state (always execute for complete toggle cycle) this->_logger.LogDebug("iobox_api::executeToggleOperation. Setting final state", secondState, __FILE__, __LINE__); if (!setValue(remote, channelName, secondState)) { this->_logger.LogError("iobox_api::executeToggleOperation. Failed to set final state", channelName, __FILE__, __LINE__); cleanup(); return false; } // Verify the final state was set correctly if (!getValue(remote, channelName, verifyValue)) { this->_logger.LogError("iobox_api::executeToggleOperation. Failed to verify final state", channelName, __FILE__, __LINE__); cleanup(); return false; } if (verifyValue != secondState) { this->_logger.LogError("iobox_api::executeToggleOperation. Final state verification failed", secondState, __FILE__, __LINE__); cleanup(); return false; } // Mark as inactive and cleanup toggleInfo->active = false; cleanup(); this->_logger.LogDebug("iobox_api::executeToggleOperation. Toggle completed successfully", channelName, __FILE__, __LINE__); return true; } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::executeToggleOperation. Exception", e.what(), __FILE__, __LINE__); cleanup(); return false; } catch (...) { this->_logger.LogFatal("iobox_api::executeToggleOperation. Unknown exception", "", __FILE__, __LINE__); cleanup(); return false; } } // Optional: Add a method to cancel active toggles bool iobox_api::cancelToggle(const std::string& remote, const std::string& channelName) { std::string key = remote + ":" + channelName; std::lock_guard mapLock(toggleMapMutex); auto it = activeToggles.find(key); if (it != activeToggles.end()) { auto toggleInfo = it->second; { std::lock_guard infoLock(toggleInfo->mtx); toggleInfo->cancelled = true; toggleInfo->active = false; toggleInfo->cv.notify_one(); } this->_logger.LogDebug("iobox_api::cancelToggle. Cancelled toggle for channel", channelName, __FILE__, __LINE__); return true; } this->_logger.LogWarn("iobox_api::cancelToggle. No active toggle found for channel", channelName, __FILE__, __LINE__); return false; } // Optional: Get status of active toggles std::vector iobox_api::getActiveToggleChannels() { std::vector activeChannels; std::lock_guard mapLock(toggleMapMutex); for (const auto& pair : activeToggles) { activeChannels.push_back(pair.first); // key is "remote:channelName" } return activeChannels; } // Optional: Get detailed status of a specific toggle bool iobox_api::getToggleStatus(const std::string& remote, const std::string& channelName, int& remainingTimeMs, std::string& currentPhase) { std::string key = remote + ":" + channelName; std::lock_guard mapLock(toggleMapMutex); auto it = activeToggles.find(key); if (it != activeToggles.end()) { auto toggleInfo = it->second; std::lock_guard infoLock(toggleInfo->mtx); auto now = std::chrono::steady_clock::now(); if (now < toggleInfo->endTime) { auto remaining = toggleInfo->endTime - now; remainingTimeMs = static_cast(std::chrono::duration_cast(remaining).count()); currentPhase = toggleInfo->revertFlag ? "waiting_to_revert" : "toggle_active"; return true; } else { remainingTimeMs = 0; currentPhase = "completing"; return true; } } remainingTimeMs = 0; currentPhase = "not_active"; return false; } // More function //{ // "request": {"action":"getStaticIpconfig", // } //} //{ // "response":{"action":"getStaticIpconfig", // "result" : {"enable":true / false, // "ip" : "192.168.1.2", // "gw" : "", // "nm" : ""} // } //} std::vector iobox_api::getStaticIpConfig(const std::string& remote) { std::vector ipConfig; std::lock_guard lock(this->_mutex); std::string ip = checkIpFromRemote(remote); if (ip == "") { this->_logger.LogError("iobox_api::getStaticIpConfig. Failed to get ip from remote: ", remote, __FILE__, __LINE__); return ipConfig; } try { iobox_profile_t* profile = findProfileByIp(ip); if (profile == nullptr) { this->_logger.LogError("iobox_api::getStaticIpConfig.", "IP address not found.", __FILE__, __LINE__); return ipConfig; } //{"request": {"action":"getStaticIpconfig"}} std::string action = "getStaticIpconfig"; std::string getStaticIpConfigMessage = "{\"request\":{\"action\":\"" + action + "\"}}"; if (!sendTcpMessage(ip, getStaticIpConfigMessage.c_str(), getStaticIpConfigMessage.length(), true)) { return ipConfig; } char revBuf[512]; size_t responseLength = sizeof(revBuf); if (!receiveTcpMessage(ip, revBuf, responseLength)) { return ipConfig; } revBuf[responseLength] = '\0'; std::string response(revBuf); std::string result; std::string reason; iobox_info_t info; if (parseResponeCommon(response, action, result, reason, info)) { std::cout << "Parse response success" << std::endl; cJSON* root = cJSON_Parse(result.c_str()); if (root == nullptr) { this->_logger.LogError("iobox_api::getStaticIpConfig.", "Failed to parse JSON", __FILE__, __LINE__); return ipConfig; } if (root->type != cJSON_Object) { this->_logger.LogError("iobox_api::getStaticIpConfig.", "Failed to get object", __FILE__, __LINE__); cJSON_Delete(root); return ipConfig; } cJSON* enableItem = cJSON_GetObjectItem(root, "enable"); cJSON* ipItem = cJSON_GetObjectItem(root, "ip"); cJSON* gwItem = cJSON_GetObjectItem(root, "gw"); cJSON* nmItem = cJSON_GetObjectItem(root, "nm"); if (enableItem == nullptr || enableItem->type != cJSON_True && enableItem->type != cJSON_False) { this->_logger.LogError("iobox_api::getStaticIpConfig.", "Failed to get enable", __FILE__, __LINE__); cJSON_Delete(root); return ipConfig; } if (ipItem == nullptr || ipItem->type != cJSON_String) { this->_logger.LogError("iobox_api::getStaticIpConfig.", "Failed to get ip", __FILE__, __LINE__); cJSON_Delete(root); return ipConfig; } if (gwItem == nullptr || gwItem->type != cJSON_String) { this->_logger.LogError("iobox_api::getStaticIpConfig.", "Failed to get gw", __FILE__, __LINE__); cJSON_Delete(root); return ipConfig; } if (nmItem == nullptr || nmItem->type != cJSON_String) { this->_logger.LogError("iobox_api::getStaticIpConfig.", "Failed to get nm", __FILE__, __LINE__); cJSON_Delete(root); return ipConfig; } ipConfig.push_back(enableItem->type == cJSON_True ? "staticIpEnable:true" : "staticIpEnable:false"); ipConfig.push_back("ip:" + std::string(ipItem->valuestring)); ipConfig.push_back("gw:" + std::string(gwItem->valuestring)); ipConfig.push_back("nm:" + std::string(nmItem->valuestring)); cJSON_Delete(root); return ipConfig; } else { this->_logger.LogError("iobox_api::getStaticIpConfig.", "Parse response failed", __FILE__, __LINE__); return ipConfig; } } catch (const std::exception& e) { std::cerr << e.what() << std::endl; this->_logger.LogFatal("iobox_api::getStaticIpConfig.", e.what(), __FILE__, __LINE__); return ipConfig; } } //{"request": {"action":"setStaticIpconfig", // "enable":true/false, // "ip":"", // "gw":"", // "nm":"" // // } //} // //{"response":{"action":"setStaticIpconfig", // "result":"", // "reason":"" // } //} bool iobox_api::setStaticIpConfig(const std::string& remote, bool enable, const std::string& ip, const std::string& gw, const std::string& nm) { std::lock_guard lock(this->_mutex); std::string ipRemote = checkIpFromRemote(remote); if (ipRemote == "") { this->_logger.LogError("iobox_api::setStaticIpConfig. Failed to get ip from remote: ", remote, __FILE__, __LINE__); return false; } if (ip != "" && !isValidIp(ip)) { std::cerr << "Invalid ip address: " << ip << std::endl; this->_logger.LogError("iobox_api::setStaticIpConfig. Invalid ip address: ", ip, __FILE__, __LINE__); return false; } if (gw != "" && !isValidIp(gw)) { this->_logger.LogError("iobox_api::setStaticIpConfig. Invalid gateway address: ", gw, __FILE__, __LINE__); return false; } if (nm != "" && !isValidIp(nm)) { this->_logger.LogError("iobox_api::setStaticIpConfig. Invalid netmask address: ", nm, __FILE__, __LINE__); return false; } try { iobox_profile_t* profile = findProfileByIp(ipRemote); if (profile == nullptr) { this->_logger.LogError("iobox_api::setStaticIpConfig. IP address not found: ", ipRemote, __FILE__, __LINE__); return false; } //{"request": {"action":"setStaticIpconfig","enable":true/false,"ip":"","gw":"","nm":""}} std::string action = "setStaticIpconfig"; std::string enableStr = enable ? "true" : "false"; std::string setStaticIpConfigMessage = "{\"request\":{\"action\":\"" + action + "\",\"enable\":" + enableStr + ",\"ip\":\"" + ip + "\",\"gw\":\"" + gw + "\",\"nm\":\"" + nm + "\"}}"; if (!sendTcpMessage(ipRemote, setStaticIpConfigMessage.c_str(), setStaticIpConfigMessage.length(), true)) { return false; } char revBuf[256]; size_t responseLength = sizeof(revBuf); if (!receiveTcpMessage(ipRemote, revBuf, responseLength)) { return false; } revBuf[responseLength] = '\0'; std::string response(revBuf); std::string result; std::string reason; iobox_info_t info; if (parseResponeCommon(response, action, result, reason, info)) { std::cout << "Parse response success" << std::endl; if (result == "success") { std::cout << "Set static ip config success" << std::endl; } else { this->_logger.LogError("iobox_api::setStaticIpConfig. Set static ip config failed: ", reason, __FILE__, __LINE__); return false; } } else { this->_logger.LogError("iobox_api::setStaticIpConfig. ", "Parse response failed", __FILE__, __LINE__); return false; } return true; } catch (const std::exception& e) { this->_logger.LogError("iobox_api::setStaticIpConfig. Exception: ", e.what(), __FILE__, __LINE__); return false; } } std::string iobox_api::getValueDataStringIoboxFromChannelName(const std::string& remote, const std::string& channelName) { std::lock_guard lock(this->_mutex); std::string ip = checkIpFromRemote(remote); if (ip == "") { this->_logger.LogError("iobox_api::getValueDataStringIoboxFromChannelName. Failed to get ip from remote: ", remote, __FILE__, __LINE__); return ""; } try { iobox_profile_t* profile = findProfileByIp(ip); if (profile == nullptr) { this->_logger.LogError("iobox_api::getValueDataStringIoboxFromChannelName. IP address not found: ", ip, __FILE__, __LINE__); return ""; } //{"request": {"action":"getValue","channel":""}} std::string action = "getValue"; std::string getValueMessage = "{\"request\":{\"action\":\"" + action + "\",\"channel\":\"" + channelName + "\"}}"; if (!sendTcpMessage(ip, getValueMessage.c_str(), getValueMessage.length(), true)) { return ""; } char revBuf[256]; size_t responseLength = sizeof(revBuf); if (!receiveTcpMessage(ip, revBuf, responseLength)) { return ""; } revBuf[responseLength] = '\0'; std::string response(revBuf); std::string result; std::string reason; iobox_info_t info; if (parseResponeCommon(response, action, result, reason, info)) { std::cout << "Parse response success" << std::endl; return result; } else { this->_logger.LogError("iobox_api::getValueDataStringIoboxFromChannelName.", " Parse response failed.", __FILE__, __LINE__); return ""; } } catch (const std::exception& e) { this->_logger.LogError("iobox_api::getValueDataStringIoboxFromChannelName.", e.what(), __FILE__, __LINE__); return ""; } } bool iobox_api::toggleDigitalOutput(const std::string& remote, const std::string& channelName, const std::string& milliseconds, bool invert, bool reset) { std::lock_guard lock(this->_mutex); std::string ip = checkIpFromRemote(remote); if (ip == "") { this->_logger.LogError("iobox_api::toggleDigitalOutput. Failed to get ip from remote: ", remote, __FILE__, __LINE__); return false; } try { iobox_profile_t* profile = findProfileByIp(ip); if (profile == nullptr) { this->_logger.LogError("iobox_api::toggleDigitalOutput. IP address not found: ", ip, __FILE__, __LINE__); return false; } //not check valid channel name here //{"request": {"action":"toggleDO", "channel":"","milliseconds":"","invert":true/false,"reset":true/false}} std::string action = "toggleDO"; std::string toggleDOMessage = "{\"request\":{\"action\":\"" + action + "\",\"channel\":\"" + channelName + "\",\"milliseconds\":\"" + milliseconds + "\",\"invert\":" + (invert ? "true" : "false") + ",\"reset\":" + (reset ? "true" : "false") + "}}"; if (!sendTcpMessage(ip, toggleDOMessage.c_str(), toggleDOMessage.length(), true)) { return false; } char revBuf[256]; size_t responseLength = sizeof(revBuf); if (!receiveTcpMessage(ip, revBuf, responseLength)) { return false; } revBuf[responseLength] = '\0'; std::string response(revBuf); std::string result; std::string reason; iobox_info_t info; if (parseResponeCommon(response, action, result, reason, info)) { std::cout << "Parse response success" << std::endl; if (result == "success") { std::cout << "Toggle digital output success" << std::endl; } else { this->_logger.LogError("iobox_api::toggleDigitalOutput. Toggle digital output failed: ", reason, __FILE__, __LINE__); return false; } } else { this->_logger.LogError("iobox_api::toggleDigitalOutput. ", "Parse response failed", __FILE__, __LINE__); return false; } return true; } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::toggleDigitalOutput. Exception: ", e.what(), __FILE__, __LINE__); return false; } } bool iobox_api::setAIBValueDataStringIoboxFromChannelName(const std::string& remote, const std::string& channelName, const std::string& value) { std::lock_guard lock(this->_mutex); std::string ip = checkIpFromRemote(remote); if (ip == "") { this->_logger.LogError("iobox_api::setAIBValueDataStringIoboxFromChannelName. Failed to get ip from remote: ", remote, __FILE__, __LINE__); return false; } try { iobox_profile_t* profile = findProfileByIp(ip); if (profile == nullptr) { this->_logger.LogError("iobox_api::setAIBValueDataStringIoboxFromChannelName. IP address not found: ", ip, __FILE__, __LINE__); return false; } //not check valid channel name here //{"request": {"action":"setAIB", "channel":"","value":""}} std::string action = "setAIB"; std::string setValueMessage = "{\"request\":{\"action\":\"" + action + "\",\"channel\":\"" + channelName + "\",\"value\":\"" + value + "\"}}"; if (!sendTcpMessage(ip, setValueMessage.c_str(), setValueMessage.length(), true)) { return false; } char revBuf[256]; size_t responseLength = sizeof(revBuf); if (!receiveTcpMessage(ip, revBuf, responseLength)) { return false; } revBuf[responseLength] = '\0'; std::string response(revBuf); std::string result; std::string reason; iobox_info_t info; if (parseResponeCommon(response, action, result, reason, info)) { std::cout << "Parse response success" << std::endl; if (result == "success") { std::cout << "Set value success" << std::endl; } else { this->_logger.LogError("iobox_api::setAIBValueDataStringIoboxFromChannelName. Set value failed: ", reason, __FILE__, __LINE__); return false; } } else { this->_logger.LogError("iobox_api::setAIBValueDataStringIoboxFromChannelName. ","Parse response failed", __FILE__, __LINE__); return false; } return true; } catch (const std::exception& e) { this->_logger.LogFatal("iobox_api::setAIBValueDataStringIoboxFromChannelName. Exception: ", e.what(), __FILE__, __LINE__); return false; } } }