#include "Device.h" #include "Util/JSON.h" #include #include #include namespace PresenceDetection { namespace UniFi { Device::Device(const std::string& hostname, int port, const std::string& username, const std::string& password, const std::string& cookieFile, int timeout, const std::string& inventoryURL, const std::string& target, const std::vector& devices) : m_loggedIn(false), m_hostname(hostname), m_port(port), m_username(username), m_password(password), m_cookieFile(cookieFile), m_timeout(timeout), m_inventoryURL(inventoryURL), m_target(target), m_staticDevices(devices) { if (!m_inventoryURL.empty()) UpdateDevicesFromInventory(); ClearDevices(); Start(); } Device::~Device() { Logout(); } void Device::Start() { m_deviceTimer.StartContinuous(5000, static_cast>(std::bind(&Device::UpdatePresentDevices, this))); if (!m_inventoryURL.empty()) m_inventoryTimer.StartContinuous(300000, static_cast>(std::bind(&Device::UpdateDevicesFromInventory, this))); } void Device::Stop() { m_deviceTimer.Stop(); if (!m_inventoryURL.empty()) m_inventoryTimer.Stop(); } void Device::Wait() { m_deviceTimer.Wait(); if (!m_inventoryURL.empty()) m_inventoryTimer.Stop(); } bool Device::Login() { std::stringstream url; url << "https://" << m_hostname << ":" << m_port << "/api/login"; Util::JSON json; json["password"] = m_password; json["username"] = m_username; try { std::vector headers; headers.push_back("Content-Type: application/json"); std::stringstream output; output << m_httpClient.GetUrlPostContents(url.str(), headers, m_cookieFile, json.dump()); Util::JSON outputJSON = Util::JSON::parse(output); if (outputJSON["meta"]["rc"] != "ok") { std::stringstream error; error << "Login Failed - " << output.str(); throw std::runtime_error(error.str()); } } catch (const std::exception& e) { std::stringstream ss; ss << "UniFi::Device::Login() - Error: " << e.what() << std::endl; Logging::Log(Logging::Severity::Error, ss.str()); std::lock_guard lock(m_mutex); m_loggedIn = false; return m_loggedIn; } std::lock_guard lock(m_mutex); m_loggedIn = true; return m_loggedIn; } void Device::Logout() { std::stringstream url; url << "https://" << m_hostname << ":" << m_port << "/logout"; std::lock_guard lock(m_mutex); m_loggedIn = false; try { m_httpClient.GetUrlSilent(url.str(), m_cookieFile); } catch (const std::exception& e) { std::stringstream ss; ss << "UniFi::Device::Logout() - Error: " << e.what() << std::endl; Logging::Log(Logging::Severity::Error, ss.str()); } } void Device::ClearDevices() { for (std::vector::iterator it = m_devices.begin(); it != m_devices.end(); ++it) { try { SendStateChange(false, *it); } catch (const std::exception& e) { std::stringstream ss; ss << "UniFi::Device::ClearDevices() - Error: " << e.what() << std::endl; Logging::Log(Logging::Severity::Error, ss.str()); } } for (std::vector::iterator it = m_staticDevices.begin(); it != m_staticDevices.end(); ++it) { if (it->HasWifiMac()) { try { SendStateChange(false, it->WifiMac()); } catch (const std::exception& e) { std::stringstream ss; ss << "UniFi::Device::ClearDevices() - Error: " << e.what() << std::endl; Logging::Log(Logging::Severity::Error, ss.str()); } } } } void Device::UpdateDevicesFromInventory() { try { std::string devices = m_httpClient.GetUrlContents(m_inventoryURL); Util::JSON json = Util::JSON::parse(devices); m_devices.clear(); for (auto& element : json) if (element["macaddress"] != "") { std::string macAddress = element["macaddress"]; std::transform(macAddress.begin(), macAddress.end(), macAddress.begin(), ::tolower); m_devices.push_back(macAddress); } } catch (const std::exception& e) { std::stringstream ss; ss << "UniFi::Device::GetDevicesFromInventory() - Error: " << e.what() << std::endl; Logging::Log(Logging::Severity::Error, ss.str()); } } void Device::UpdatePresentDevices() { bool loggedIn; { std::lock_guard lock(m_mutex); loggedIn = m_loggedIn; } if (!loggedIn) if (!Login()) return; std::stringstream url; url << "https://" << m_hostname << ":" << m_port << "/api/s/default/stat/sta"; std::time_t timeStamp = std::time(nullptr); std::vector presentDevices; std::vector addedDevices; std::vector removedDevices; try { std::stringstream output; output << m_httpClient.GetUrlContents(url.str(), m_cookieFile); Util::JSON json = Util::JSON::parse(output); if (json["meta"]["rc"] != "ok") { std::stringstream error; error << "Query Failed"; throw std::runtime_error(error.str()); } for (auto& device : json["data"]) { std::string macAddress = device["mac"]; std::transform(macAddress.begin(), macAddress.end(), macAddress.begin(), ::tolower); int lastSeen = device["last_seen"]; if (std::find(m_devices.begin(), m_devices.end(), macAddress) != m_devices.end()) { if ((timeStamp - lastSeen) < m_timeout) { if (std::find(m_presentDevices.begin(), m_presentDevices.end(), macAddress) == m_presentDevices.end()) addedDevices.push_back(macAddress); presentDevices.push_back(macAddress); } } for (std::vector::iterator it = m_staticDevices.begin(); it != m_staticDevices.end(); ++it) { if ((timeStamp - lastSeen) < m_timeout) { if (it->HasWifiMac() && it->WifiMac() == macAddress) { if (std::find(m_presentDevices.begin(), m_presentDevices.end(), macAddress) == m_presentDevices.end()) addedDevices.push_back(macAddress); presentDevices.push_back(macAddress); } } } } } catch (const std::exception& e) { std::stringstream ss; ss << "UniFi::Device::IsDevicePresent() - Error: " << e.what() << std::endl; Logging::Log(Logging::Severity::Error, ss.str()); Logout(); return; } for (std::vector::iterator it = m_presentDevices.begin(); it != m_presentDevices.end(); ++it) if (std::find(presentDevices.begin(), presentDevices.end(), *it) == presentDevices.end()) removedDevices.push_back(*it); for (std::vector::iterator it = addedDevices.begin(); it != addedDevices.end(); ++it) SendStateChange(true, *it); for (std::vector::iterator it = removedDevices.begin(); it != removedDevices.end(); ++it) SendStateChange(false, *it); m_presentDevices.assign(presentDevices.begin(), presentDevices.end()); } void Device::SendStateChange(bool present, const std::string& macAddress) { char sign; if (present) sign = '+'; else sign = '-'; std::stringstream ss; ss << "UniFi: " << sign << " " << macAddress; Logging::Log(Logging::Severity::Info, ss.str()); if (!m_target.empty()) { std::stringstream url; url << m_target << "/UniFi/" << sign << "/" << macAddress; try { m_httpClient.GetUrlSilent(url.str()); } catch (const std::exception& e) { std::stringstream ss; ss << "UniFi::Device::SendStateChange() - Error: " << e.what() << std::endl; Logging::Log(Logging::Severity::Error, ss.str()); } } for (std::vector::iterator it = m_staticDevices.begin(); it != m_staticDevices.end(); ++it) { if (it->HasWifiMac() && it->WifiMac() == macAddress) { it->SetWifiState(present); std::vector urls; if (present) { if (!it->GetBluetoothState() && it->HasOnlineURL()) urls.push_back(it->OnlineURL()); if (it->HasWifiOnlineURL()) urls.push_back(it->WifiOnlineURL()); } else if (!present) { if (!it->GetBluetoothState() && it->HasOfflineURL()) urls.push_back(it->OfflineURL()); if (it->HasWifiOfflineURL()) urls.push_back(it->WifiOfflineURL()); } for (auto& url : urls) { try { m_httpClient.GetUrlSilent(url); } catch (const std::exception& e) { std::stringstream ss; ss << "UniFi::Device::SendStateChange() - Error: " << e.what() << std::endl; Logging::Log(Logging::Severity::Error, ss.str()); } } } } } } // namespace UniFi } // namespace PresenceDetection