diff --git a/Application/PresenceDetection.cc b/Application/PresenceDetection.cc index 4ac7eac..7785b22 100644 --- a/Application/PresenceDetection.cc +++ b/Application/PresenceDetection.cc @@ -113,7 +113,14 @@ int main(int argc, char** argv) else throw std::runtime_error("UniFi CookieFile directive missing in ini file."); - pUniFiDevice = std::make_shared(hostname, username, password, cookiefile, target); + std::string inventoryURL; + boost::optional bluetoothInventoryURL = pt.get_child_optional("UniFi.Inventory"); + if (bluetoothInventoryURL) + inventoryURL = pt.get("UniFi.Inventory"); + else + throw std::runtime_error("UniFi Inventory directive missing in ini file."); + + pUniFiDevice = std::make_shared(hostname, username, password, cookiefile, inventoryURL, target); } std::shared_ptr pBluetoothDevice; diff --git a/Application/Test.cc b/Application/Test.cc index 7843909..a0bfb65 100644 --- a/Application/Test.cc +++ b/Application/Test.cc @@ -1,3 +1,4 @@ +#include #include #include @@ -16,6 +17,8 @@ #include #include +#include "Util/Timer.h" + /* Defaults */ static bdaddr_t bdaddr; static int size = 44; @@ -224,13 +227,41 @@ error: exit(1); } +void print() +{ + std::cout << "Print" << std::endl; +} + int main(int /*argc*/, char** /*argv[]*/) { - std::string address = "48:4B:AA:85:30:C5"; - ping(address.c_str()); + //std::string address = "48:4B:AA:85:30:C5"; + //ping(address.c_str()); //address = "d0:c5:f3:84:99:de"; //ping(address.c_str()); + PresenceDetection::Util::Timer timer; + std::function f_print = print; + + std::cout << "---" << std::endl; + timer.StartContinuous(1000, f_print); + timer.Stop(); + timer.Wait(); + std::cout << "---" << std::endl << std::endl; + + std::cout << "---" << std::endl; + timer.StartContinuous(3000, f_print); + timer.Abort(); + timer.Wait(); + std::cout << "---" << std::endl << std::endl; + + std::cout << "---" << std::endl; + timer.StartContinuous(3000, f_print); + sleep(1); + timer.Abort(); + timer.Wait(); + std::cout << "---" << std::endl << std::endl; + return 0; } + diff --git a/Bluetooth/Device.cpp b/Bluetooth/Device.cpp index c5523c1..7b6f7d6 100644 --- a/Bluetooth/Device.cpp +++ b/Bluetooth/Device.cpp @@ -1,6 +1,6 @@ +#include #include #include -#include #include "Util/JSON.h" #include "Functions.h" #include "Device.h" @@ -10,15 +10,10 @@ namespace PresenceDetection { namespace Bluetooth { Device::Device(const std::string& inventoryURL, const std::string& target) : + m_inventoryURL(inventoryURL), m_target(target) { - std::string devices = m_httpClient.GetUrlContents(inventoryURL); - Util::JSON json = Util::JSON::parse(devices); - - for (auto& element : json) - if (element["bluetooth"] != "") - m_devices.push_back(element["bluetooth"]); - + UpdateDevicesFromInventory(); Start(); } @@ -28,17 +23,42 @@ Device::~Device() void Device::Start() { - m_timer.StartContinuous(10000, static_cast>(std::bind(&Device::UpdatePresentDevices, this))); + m_deviceTimer.StartContinuous(10000, static_cast>(std::bind(&Device::UpdatePresentDevices, this))); + m_inventoryTimer.StartContinuous(300000, static_cast>(std::bind(&Device::UpdateDevicesFromInventory, this))); } void Device::Stop() { - m_timer.Stop(); + m_deviceTimer.Stop(); } void Device::Wait() { - m_timer.Wait(); + m_deviceTimer.Wait(); +} + +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["bluetooth"] != "") + { + std::string bluetooth = element["bluetooth"]; + std::transform(bluetooth.begin(), bluetooth.end(), bluetooth.begin(), ::tolower); + m_devices.push_back(bluetooth); + } + } + catch (const std::exception& e) + { + std::stringstream ss; + ss << "Bluetooth::Device::GetDevicesFromInventory() - Error: " << e.what() << std::endl; + syslog(LOG_ERR, "%s", ss.str().c_str()); + } } void Device::UpdatePresentDevices() @@ -49,11 +69,20 @@ void Device::UpdatePresentDevices() for (std::vector::iterator it = m_devices.begin(); it != m_devices.end(); ++it) { - if (Functions::Ping(*it)) + try { - if (std::find(m_presentDevices.begin(), m_presentDevices.end(), *it) == m_presentDevices.end()) - SendStateChange(true, *it); - presentDevices.push_back(*it); + if (Functions::Ping(*it)) + { + if (std::find(m_presentDevices.begin(), m_presentDevices.end(), *it) == m_presentDevices.end()) + SendStateChange(true, *it); + presentDevices.push_back(*it); + } + } + catch (const std::exception& e) + { + std::stringstream ss; + ss << "Bluetooth::Device::UpdatePresentDevices() - Error: " << e.what() << std::endl; + syslog(LOG_ERR, "%s", ss.str().c_str()); } } diff --git a/Bluetooth/Device.h b/Bluetooth/Device.h index 9b1074f..6af8799 100644 --- a/Bluetooth/Device.h +++ b/Bluetooth/Device.h @@ -21,15 +21,18 @@ public: void Wait(); private: + void UpdateDevicesFromInventory(); void UpdatePresentDevices(); void SendStateChange(bool present, const std::string& macAddress); private: - Util::Timer m_timer; + Util::Timer m_deviceTimer; + Util::Timer m_inventoryTimer; Util::HttpClient m_httpClient; std::vector m_devices; + std::string m_inventoryURL; std::string m_target; std::vector m_presentDevices; diff --git a/PresenceDetection.ini b/PresenceDetection.ini index 9278f3c..0a112fa 100644 --- a/PresenceDetection.ini +++ b/PresenceDetection.ini @@ -8,6 +8,7 @@ Hostname = UniFi Username = Password = CookieFile = /tmp/UniFi_Cookies +Inventory = [Bluetooth] Inventory = diff --git a/UniFi/Device.cpp b/UniFi/Device.cpp index 6f63e39..6e27c88 100644 --- a/UniFi/Device.cpp +++ b/UniFi/Device.cpp @@ -1,22 +1,23 @@ +#include #include -#include -#include -#include +#include "Util/JSON.h" #include "Device.h" namespace PresenceDetection { namespace UniFi { -Device::Device(const std::string& hostname, const std::string& username, const std::string& password, const std::string& cookieFile, const std::string& target) : +Device::Device(const std::string& hostname, const std::string& username, const std::string& password, const std::string& cookieFile, const std::string& inventoryURL, const std::string& target) : m_loggedIn(false), m_hostname(hostname), m_username(username), m_password(password), m_cookieFile(cookieFile), - m_target(target) + m_inventoryURL(inventoryURL), + m_target(target), + m_offlineTimeout(120) { - m_offlineTimeout = 120; + UpdateDevicesFromInventory(); Start(); } @@ -27,17 +28,18 @@ Device::~Device() void Device::Start() { - m_timer.StartContinuous(5000, static_cast>(std::bind(&Device::UpdatePresentDevices, this))); + m_deviceTimer.StartContinuous(5000, static_cast>(std::bind(&Device::UpdatePresentDevices, this))); + m_inventoryTimer.StartContinuous(300000, static_cast>(std::bind(&Device::UpdateDevicesFromInventory, this))); } void Device::Stop() { - m_timer.Stop(); + m_deviceTimer.Stop(); } void Device::Wait() { - m_timer.Wait(); + m_deviceTimer.Wait(); } bool Device::Login() @@ -45,23 +47,18 @@ bool Device::Login() std::stringstream url; url << "https://" << m_hostname << "/api/login"; - std::ostringstream json; - boost::property_tree::ptree root; - root.put("password", m_password); - root.put("username", m_username); - boost::property_tree::write_json(json, root); + Util::JSON json; + json["password"] = m_password; + json["username"] = m_username; try { std::stringstream output; - output << m_httpClient.GetUrlPostContents(url.str(), m_cookieFile, json.str(), "application/json"); + output << m_httpClient.GetUrlPostContents(url.str(), m_cookieFile, json.dump(), "application/json"); - boost::property_tree::ptree pt; - boost::property_tree::read_json(output, pt); + Util::JSON outputJSON = Util::JSON::parse(output); - std::string result = pt.get_child("meta").get("rc"); - - if (result != "ok") + if (outputJSON["meta"]["rc"] != "ok") { std::stringstream error; error << "Login Failed - " << output.str(); @@ -103,6 +100,30 @@ void Device::Logout() } } +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; + syslog(LOG_ERR, "%s", ss.str().c_str()); + } +} + void Device::UpdatePresentDevices() { bool loggedIn; @@ -128,28 +149,28 @@ void Device::UpdatePresentDevices() std::stringstream output; output << m_httpClient.GetUrlContents(url.str(), m_cookieFile); - boost::property_tree::ptree pt; - boost::property_tree::read_json(output, pt); + Util::JSON json = Util::JSON::parse(output); - std::string result = pt.get_child("meta").get("rc"); - - if (result != "ok") + if (json["meta"]["rc"] != "ok") { std::stringstream error; error << "Query Failed"; throw std::runtime_error(error.str()); } - BOOST_FOREACH(boost::property_tree::ptree::value_type &v, pt.get_child("data")) + for (auto& device : json["data"]) { - std::string macAddress = v.second.get("mac"); - int lastSeen = v.second.get("last_seen"); + std::string macAddress = device["mac"]; + int lastSeen = device["last_seen"]; if ((timeStamp - lastSeen) < m_offlineTimeout) { - if (std::find(m_presentDevices.begin(), m_presentDevices.end(), macAddress) == m_presentDevices.end()) - addedDevices.push_back(macAddress); - presentDevices.push_back(macAddress); + if (std::find(m_devices.begin(), m_devices.end(), macAddress) != m_devices.end()) + { + if (std::find(m_presentDevices.begin(), m_presentDevices.end(), macAddress) == m_presentDevices.end()) + addedDevices.push_back(macAddress); + presentDevices.push_back(macAddress); + } } } } diff --git a/UniFi/Device.h b/UniFi/Device.h index 62ea203..599ea90 100644 --- a/UniFi/Device.h +++ b/UniFi/Device.h @@ -14,7 +14,7 @@ namespace UniFi { class Device { public: - Device(const std::string& hostname, const std::string& username, const std::string& password, const std::string& cookieFile, const std::string& target); + Device(const std::string& hostname, const std::string& username, const std::string& password, const std::string& cookieFile, const std::string& inventoryURL, const std::string& target); ~Device(); void Start(); @@ -25,11 +25,13 @@ private: bool Login(); void Logout(); + void UpdateDevicesFromInventory(); void UpdatePresentDevices(); void SendStateChange(bool present, const std::string& macAddress); private: - Util::Timer m_timer; + Util::Timer m_deviceTimer; + Util::Timer m_inventoryTimer; Util::HttpClient m_httpClient; std::mutex m_mutex; @@ -39,6 +41,9 @@ private: std::string m_password; std::string m_cookieFile; + std::vector m_devices; + + std::string m_inventoryURL; std::string m_target; int m_offlineTimeout; diff --git a/Util/Timer.cpp b/Util/Timer.cpp index 0d2b822..3228bf6 100644 --- a/Util/Timer.cpp +++ b/Util/Timer.cpp @@ -1,3 +1,5 @@ +#include + #include #include "Util.h" #include "Timer.h" @@ -8,12 +10,14 @@ namespace Util { Timer::Timer() : m_run(false), + m_aborted(false), m_identifier(CreateRandomString(10)) { } Timer::~Timer() { + Abort(); Wait(); } @@ -57,6 +61,17 @@ void Timer::Stop() m_run.store(false, std::memory_order_release); } +void Timer::Abort() +{ + std::cout << "Timer::Abort()" << std::endl; + m_run.store(false, std::memory_order_release); + { + std::unique_lock lock(m_mutex); + m_aborted = true; + } + m_conditionVariable.notify_all(); +} + bool Timer::IsRunning() const noexcept { return (m_run.load(std::memory_order_acquire) && m_thread.joinable()); diff --git a/Util/Timer.h b/Util/Timer.h index 723ff8f..dafabe9 100644 --- a/Util/Timer.h +++ b/Util/Timer.h @@ -2,7 +2,9 @@ #define UTIL_TIMER_H #include +#include #include +#include #include #include "Helpers.h" @@ -30,18 +32,19 @@ public: std::string Identifier() const; template - void StartSingle(int interval, std::function const & function, Arguments && ...arguments) + void StartSingle(int milliSeconds, std::function const & function, Arguments && ...arguments) { - Start(TimerType::Single, interval, function, std::forward(arguments)...); + Start(TimerType::Single, milliSeconds, function, std::forward(arguments)...); } template - void StartContinuous(int interval, std::function const & function, Arguments && ...arguments) + void StartContinuous(int milliSeconds, std::function const & function, Arguments && ...arguments) { - Start(TimerType::Continuous, interval, function, std::forward(arguments)...); + Start(TimerType::Continuous, milliSeconds, function, std::forward(arguments)...); } void Stop(); + void Abort(); bool IsRunning() const noexcept; void Wait(); @@ -59,7 +62,7 @@ private: private: template - void Start(TimerType::type type, int interval, std::function const & function, Arguments && ...arguments) + void Start(TimerType::type type, int milliSeconds, std::function const & function, Arguments && ...arguments) { if (type == TimerType::Unknown) return; @@ -67,17 +70,21 @@ private: if (m_run.load(std::memory_order_acquire)) Stop(); - m_thread = std::thread([this, type, interval, function, &arguments...]() + m_thread = std::thread([this, type, milliSeconds, function, &arguments...]() { auto argumentsCopy = std::make_tuple(std::forward(arguments)...); m_run.store(true, std::memory_order_release); + m_aborted = false; while (m_run.load(std::memory_order_acquire)) { - std::this_thread::sleep_for(std::chrono::milliseconds(interval)); - Helpers::execute(function, argumentsCopy); - if (type == TimerType::Single) - m_run.store(false, std::memory_order_release); + std::unique_lock lock(m_mutex); + if (!m_conditionVariable.wait_for(lock, std::chrono::milliseconds(milliSeconds), [&]{return m_aborted;})) + { + Helpers::execute(function, argumentsCopy); + if (type == TimerType::Single) + m_run.store(false, std::memory_order_release); + } } }); @@ -88,6 +95,9 @@ private: private: std::atomic m_run; + bool m_aborted; + std::mutex m_mutex; + std::condition_variable m_conditionVariable; std::string m_identifier; std::thread m_thread; };