diff --git a/Controllers/GoveeController/GoveeController.cpp b/Controllers/GoveeController/GoveeController.cpp index ba806963d..7ead83f60 100644 --- a/Controllers/GoveeController/GoveeController.cpp +++ b/Controllers/GoveeController/GoveeController.cpp @@ -1,9 +1,12 @@ /*---------------------------------------------------------*\ -| GoveeController.cpp | +| GoveeController.cpp | | | -| Driver for Govee controller | +| Driver for Govee wireless lighting devices | | | -| Adam Honse (calcprogrammer1@gmail.com), 12/1/2023 | +| Adam Honse (calcprogrammer1@gmail.com) 01 Dec 2023 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | \*---------------------------------------------------------*/ #include @@ -27,54 +30,46 @@ base64::byte CalculateXorChecksum(std::vector packet) GoveeController::GoveeController(std::string ip) { - /*-----------------------------------------------------------------*\ - | Fill in location string with device's IP address | - \*-----------------------------------------------------------------*/ + /*-----------------------------------------------------*\ + | Fill in location string with device's IP address | + \*-----------------------------------------------------*/ ip_address = ip; - /*-----------------------------------------------------------------*\ - | Open a UDP client sending to and receiving from the Govee | - | Multicast IP, send port 4001 and receive port 4002 | - \*-----------------------------------------------------------------*/ - broadcast_port.udp_client("239.255.255.250", "4001", "4002"); - broadcast_port.udp_join_multicast_group("239.255.255.250"); + /*-----------------------------------------------------*\ + | Register callback for receiving broadcasts | + \*-----------------------------------------------------*/ + RegisterReceiveBroadcastCallback(this); - /*-----------------------------------------------------------------*\ - | Start a thread to handle responses received from the Wiz device | - \*-----------------------------------------------------------------*/ - ReceiveThreadRun = true; - ReceiveThread = new std::thread(&GoveeController::ReceiveThreadFunction, this); + broadcast_received = false; - /*-----------------------------------------------------------------*\ - | Request device information | - \*-----------------------------------------------------------------*/ + /*-----------------------------------------------------*\ + | Request device information | + \*-----------------------------------------------------*/ SendScan(); - /*-----------------------------------------------------------------*\ - | Wait up to 5s for device information to be received | - \*-----------------------------------------------------------------*/ + /*-----------------------------------------------------*\ + | Wait up to 5s for device information to be received | + \*-----------------------------------------------------*/ for(unsigned int wait_count = 0; wait_count < 500; wait_count++) { - if(ReceiveThreadRun.load() == false) + if(broadcast_received) { - ReceiveThread->join(); break; } std::this_thread::sleep_for(10ms); } - /*-----------------------------------------------------------------*\ - | Open a UDP client sending to the Govee device IP, port 4003 | - \*-----------------------------------------------------------------*/ + /*-----------------------------------------------------*\ + | Open a UDP client sending to the Govee device IP, | + | port 4003 | + \*-----------------------------------------------------*/ port.udp_client(ip_address.c_str(), "4003"); } GoveeController::~GoveeController() { - ReceiveThreadRun = 0; - ReceiveThread->join(); - delete ReceiveThread; + UnregisterReceiveBroadcastCallback(this); } std::string GoveeController::GetLocation() @@ -95,79 +90,70 @@ std::string GoveeController::GetVersion() "WiFI Software Version: " + wifiVersionSoft + "\r\n"); } -void GoveeController::ReceiveThreadFunction() +void GoveeController::ReceiveBroadcast(char* recv_buf, int size) { - char recv_buf[1024]; - - while(ReceiveThreadRun.load()) + if(broadcast_received) { - /*-----------------------------------------------------------------*\ - | Receive up to 1024 bytes from the device with a 1s timeout | - \*-----------------------------------------------------------------*/ - int size = broadcast_port.udp_listen(recv_buf, 1024); + return; + } - if(size > 0) + /*-----------------------------------------------------*\ + | Responses are not null-terminated, so add termination | + \*-----------------------------------------------------*/ + recv_buf[size] = '\0'; + + /*-----------------------------------------------------*\ + | Convert null-terminated response to JSON | + \*-----------------------------------------------------*/ + json response = json::parse(recv_buf); + + /*-----------------------------------------------------*\ + | Check if the response contains the method name | + \*-----------------------------------------------------*/ + if(response.contains("msg")) + { + /*-------------------------------------------------*\ + | Handle responses for scan command | + | This command's response should contain a msg | + | object containing a data member with ip, device, | + | sku, among others. | + \*-------------------------------------------------*/ + if(response["msg"].contains("cmd")) { - /*-----------------------------------------------------------------*\ - | Responses are not null-terminated, so add termination | - \*-----------------------------------------------------------------*/ - recv_buf[size] = '\0'; - - printf( "response %s \r\n", recv_buf); - - /*-----------------------------------------------------------------*\ - | Convert null-terminated response to JSON | - \*-----------------------------------------------------------------*/ - json response = json::parse(recv_buf); - - /*-----------------------------------------------------------------*\ - | Check if the response contains the method name | - \*-----------------------------------------------------------------*/ - if(response.contains("msg")) + if(response["msg"]["cmd"] == "scan") { - /*-------------------------------------------------------------*\ - | Handle responses for scan command | - | This command's response should contain a msg object | - | containing a data member with ip, device, sku, among others. | - \*-------------------------------------------------------------*/ - if(response["msg"].contains("cmd")) + if(response["msg"].contains("data")) { - if(response["msg"]["cmd"] == "scan") + if(response["msg"]["data"].contains("ip")) { - if(response["msg"].contains("data")) + if(response["msg"]["data"]["ip"] == ip_address) { - if(response["msg"]["data"].contains("ip")) + if(response["msg"]["data"].contains("sku")) { - if(response["msg"]["data"]["ip"] == ip_address) - { - if(response["msg"]["data"].contains("sku")) - { - sku = response["msg"]["data"]["sku"]; - } - - if(response["msg"]["data"].contains("bleVersionHard")) - { - bleVersionHard = response["msg"]["data"]["bleVersionHard"]; - } - - if(response["msg"]["data"].contains("bleVersionSoft")) - { - bleVersionSoft = response["msg"]["data"]["bleVersionSoft"]; - } - - if(response["msg"]["data"].contains("wifiVersionHard")) - { - wifiVersionHard = response["msg"]["data"]["wifiVersionHard"]; - } - - if(response["msg"]["data"].contains("wifiVersionSoft")) - { - wifiVersionSoft = response["msg"]["data"]["wifiVersionSoft"]; - } - - ReceiveThreadRun = false; - } + sku = response["msg"]["data"]["sku"]; } + + if(response["msg"]["data"].contains("bleVersionHard")) + { + bleVersionHard = response["msg"]["data"]["bleVersionHard"]; + } + + if(response["msg"]["data"].contains("bleVersionSoft")) + { + bleVersionSoft = response["msg"]["data"]["bleVersionSoft"]; + } + + if(response["msg"]["data"].contains("wifiVersionHard")) + { + wifiVersionHard = response["msg"]["data"]["wifiVersionHard"]; + } + + if(response["msg"]["data"].contains("wifiVersionSoft")) + { + wifiVersionSoft = response["msg"]["data"]["wifiVersionSoft"]; + } + + broadcast_received = true; } } } @@ -186,9 +172,9 @@ void GoveeController::SetColor(unsigned char red, unsigned char green, unsigned command["msg"]["data"]["color"]["b"] = blue; command["msg"]["data"]["colorTemInKelvin"] = "0"; - /*-----------------------------------------------------------------*\ - | Convert the JSON object to a string and write it | - \*-----------------------------------------------------------------*/ + /*-----------------------------------------------------*\ + | Convert the JSON object to a string and write it | + \*-----------------------------------------------------*/ std::string command_str = command.dump(); port.udp_write((char *)command_str.c_str(), command_str.length() + 1); @@ -215,9 +201,9 @@ void GoveeController::SendRazerData(RGBColor* colors, unsigned int size) command["msg"]["cmd"] = "razer"; command["msg"]["data"]["pt"] = base64::encode(pkt); - /*-----------------------------------------------------------------*\ - | Convert the JSON object to a string and write it | - \*-----------------------------------------------------------------*/ + /*-----------------------------------------------------*\ + | Convert the JSON object to a string and write it | + \*-----------------------------------------------------*/ std::string command_str = command.dump(); port.udp_write((char *)command_str.c_str(), command_str.length() + 1); @@ -231,9 +217,9 @@ void GoveeController::SendRazerDisable() command["msg"]["cmd"] = "razer"; command["msg"]["data"]["pt"] = base64::encode(pkt); - /*-----------------------------------------------------------------*\ - | Convert the JSON object to a string and write it | - \*-----------------------------------------------------------------*/ + /*-----------------------------------------------------*\ + | Convert the JSON object to a string and write it | + \*-----------------------------------------------------*/ std::string command_str = command.dump(); port.udp_write((char *)command_str.c_str(), command_str.length() + 1); @@ -247,9 +233,9 @@ void GoveeController::SendRazerEnable() command["msg"]["cmd"] = "razer"; command["msg"]["data"]["pt"] = base64::encode(pkt); - /*-----------------------------------------------------------------*\ - | Convert the JSON object to a string and write it | - \*-----------------------------------------------------------------*/ + /*-----------------------------------------------------*\ + | Convert the JSON object to a string and write it | + \*-----------------------------------------------------*/ std::string command_str = command.dump(); port.udp_write((char *)command_str.c_str(), command_str.length() + 1); @@ -262,10 +248,74 @@ void GoveeController::SendScan() command["msg"]["cmd"] = "scan"; command["msg"]["data"]["account_topic"] = "GA/123456789"; - /*-----------------------------------------------------------------*\ - | Convert the JSON object to a string and write it | - \*-----------------------------------------------------------------*/ + /*-----------------------------------------------------*\ + | Convert the JSON object to a string and write it | + \*-----------------------------------------------------*/ std::string command_str = command.dump(); broadcast_port.udp_write((char *)command_str.c_str(), command_str.length() + 1); } + +/*---------------------------------------------------------*\ +| Static class members for shared broadcast receiver | +\*---------------------------------------------------------*/ +net_port GoveeController::broadcast_port; +std::vector GoveeController::callbacks; +std::thread* GoveeController::ReceiveThread; +std::atomic GoveeController::ReceiveThreadRun; + +void GoveeController::ReceiveBroadcastThreadFunction() +{ + char recv_buf[1024]; + + broadcast_port.set_receive_timeout(1, 0); + + while(ReceiveThreadRun.load()) + { + /*-------------------------------------------------*\ + | Receive up to 1024 bytes from the device with a | + | 1s timeout | + \*-------------------------------------------------*/ + int size = broadcast_port.udp_listen(recv_buf, 1024); + + /*-------------------------------------------------*\ + | If data was received, loop through registered | + | callback controllers and call the | + | ReceiveBroadcast function for the controller | + | matching the received data | + | | + | NOTE: As implemented, it doesn't actually match | + | the intended controller and just calls all | + | registered controllers. As they are all called | + | sequence, this should work, but if parallel calls | + | are ever needed, receives should be filtered by | + | IP address | + \*-------------------------------------------------*/ + if(size > 0) + { + for(std::size_t callback_idx = 0; callback_idx < callbacks.size(); callback_idx++) + { + GoveeController* controller = callbacks[callback_idx]; + + controller->ReceiveBroadcast(recv_buf, size); + } + } + } +} + +void GoveeController::RegisterReceiveBroadcastCallback(GoveeController* controller_ptr) +{ + callbacks.push_back(controller_ptr); +} + +void GoveeController::UnregisterReceiveBroadcastCallback(GoveeController* controller_ptr) +{ + for(std::size_t callback_idx = 0; callback_idx < callbacks.size(); callback_idx++) + { + if(callbacks[callback_idx] == controller_ptr) + { + callbacks.erase(callbacks.begin() + callback_idx); + break; + } + } +} diff --git a/Controllers/GoveeController/GoveeController.h b/Controllers/GoveeController/GoveeController.h index 0bc1cf1f2..ba0ff1d0f 100644 --- a/Controllers/GoveeController/GoveeController.h +++ b/Controllers/GoveeController/GoveeController.h @@ -1,19 +1,21 @@ /*---------------------------------------------------------*\ -| GoveeController.h | +| GoveeController.h | | | -| Definitions for Govee controller | +| Driver for Govee wireless lighting devices | | | -| Adam Honse (calcprogrammer1@gmail.com), 12/1/2023 | +| Adam Honse (calcprogrammer1@gmail.com) 01 Dec 2023 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | \*---------------------------------------------------------*/ #pragma once -#include "RGBController.h" -#include "net_port.h" - #include #include #include +#include "RGBController.h" +#include "net_port.h" class GoveeController { @@ -25,7 +27,7 @@ public: std::string GetSku(); std::string GetVersion(); - void ReceiveThreadFunction(); + void ReceiveBroadcast(char* recv_buf, int size); void SendRazerData(RGBColor* colors, unsigned int size); void SendRazerDisable(); @@ -47,9 +49,23 @@ private: std::string wifiVersionHard; std::string wifiVersionSoft; - net_port port; - net_port broadcast_port; + bool broadcast_received; - std::thread* ReceiveThread; - std::atomic ReceiveThreadRun; + net_port port; + +public: + /*-----------------------------------------------------*\ + | One receive thread is shared among all instances of | + | GoveeController, so the receive thread function is | + | static and the thread is initialized in the detector | + | if any GoveeControllers are created. | + \*-----------------------------------------------------*/ + static net_port broadcast_port; + static std::vector callbacks; + static std::thread* ReceiveThread; + static std::atomic ReceiveThreadRun; + + static void ReceiveBroadcastThreadFunction(); + static void RegisterReceiveBroadcastCallback(GoveeController* controller_ptr); + static void UnregisterReceiveBroadcastCallback(GoveeController* controller_ptr); }; diff --git a/Controllers/GoveeController/GoveeControllerDetect.cpp b/Controllers/GoveeController/GoveeControllerDetect.cpp index 832094ac5..e949b99a9 100644 --- a/Controllers/GoveeController/GoveeControllerDetect.cpp +++ b/Controllers/GoveeController/GoveeControllerDetect.cpp @@ -1,11 +1,22 @@ +/*---------------------------------------------------------*\ +| GoveeControllerDetect.cpp | +| | +| Detector for Govee wireless lighting devices | +| | +| Adam Honse (calcprogrammer1@gmail.com) 01 Dec 2023 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ + +#include +#include +#include #include "Detector.h" #include "GoveeController.h" #include "RGBController.h" #include "RGBController_Govee.h" #include "SettingsManager.h" -#include -#include -#include /******************************************************************************************\ * * @@ -19,16 +30,36 @@ void DetectGoveeControllers() { json govee_settings; - /*-------------------------------------------------*\ - | Get Govee settings from settings manager | - \*-------------------------------------------------*/ + /*-----------------------------------------------------*\ + | Get Govee settings from settings manager | + \*-----------------------------------------------------*/ govee_settings = ResourceManager::get()->GetSettingsManager()->GetSettings("GoveeDevices"); - /*-------------------------------------------------*\ - | If the Govee settings contains devices, process | - \*-------------------------------------------------*/ + /*-----------------------------------------------------*\ + | If the Govee settings contains devices, process | + \*-----------------------------------------------------*/ if(govee_settings.contains("devices")) { + GoveeController::ReceiveThreadRun = false; + + if(govee_settings["devices"].size() > 0) + { + /*---------------------------------------------*\ + | Open a UDP client sending to and receiving | + | from the Govee Multicast IP, send port 4001 | + | and receive port 4002 | + \*---------------------------------------------*/ + GoveeController::broadcast_port.udp_client("239.255.255.250", "4001", "4002"); + GoveeController::broadcast_port.udp_join_multicast_group("239.255.255.250"); + + /*---------------------------------------------*\ + | Start a thread to handle responses received | + | from the Govee device | + \*---------------------------------------------*/ + GoveeController::ReceiveThreadRun = true; + GoveeController::ReceiveThread = new std::thread(&GoveeController::ReceiveBroadcastThreadFunction); + } + for(unsigned int device_idx = 0; device_idx < govee_settings["devices"].size(); device_idx++) { if(govee_settings["devices"][device_idx].contains("ip")) @@ -41,6 +72,19 @@ void DetectGoveeControllers() ResourceManager::get()->RegisterRGBController(rgb_controller); } } + + /*-------------------------------------------------*\ + | All controllers have been created, the broadcast | + | receiver thread is no longer needed and can be | + | shut down | + \*-------------------------------------------------*/ + if(GoveeController::ReceiveThreadRun) + { + GoveeController::ReceiveThreadRun = false; + GoveeController::ReceiveThread->join(); + delete GoveeController::ReceiveThread; + GoveeController::broadcast_port.tcp_close(); + } } } /* DetectGoveeControllers() */ diff --git a/Controllers/GoveeController/RGBController_Govee.cpp b/Controllers/GoveeController/RGBController_Govee.cpp index 47e3b6e1b..26a9bc627 100644 --- a/Controllers/GoveeController/RGBController_Govee.cpp +++ b/Controllers/GoveeController/RGBController_Govee.cpp @@ -1,10 +1,13 @@ -/*-----------------------------------------*\ -| RGBController_Govee.cpp | -| | -| Generic RGB Interface for Govee | -| | -| Adam Honse (CalcProgrammer1) 12/27/2023 | -\*-----------------------------------------*/ +/*---------------------------------------------------------*\ +| RGBController_Govee.cpp | +| | +| RGBController for Govee wireless lighting devices | +| | +| Adam Honse (calcprogrammer1@gmail.com) 27 Dec 2023 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ #include #include "RGBController_Govee.h" @@ -130,10 +133,10 @@ void RGBController_Govee::KeepaliveThread() { while(keepalive_thread_run.load()) { - if((std::chrono::steady_clock::now() - last_update_time) > std::chrono::milliseconds(500)) + if((std::chrono::steady_clock::now() - last_update_time) > std::chrono::seconds(30)) { DeviceUpdateLEDs(); } - std::this_thread::sleep_for(100ms); + std::this_thread::sleep_for(10s); } } diff --git a/Controllers/GoveeController/RGBController_Govee.h b/Controllers/GoveeController/RGBController_Govee.h index 3137f4f21..6c1809ba2 100644 --- a/Controllers/GoveeController/RGBController_Govee.h +++ b/Controllers/GoveeController/RGBController_Govee.h @@ -1,10 +1,13 @@ -/*-----------------------------------------*\ -| RGBController_Govee.h | -| | -| Generic RGB Interface for Govee | -| | -| Adam Honse (CalcProgrammer1) 12/1/2023 | -\*-----------------------------------------*/ +/*---------------------------------------------------------*\ +| RGBController_Govee.h | +| | +| RGBController for Govee wireless lighting devices | +| | +| Adam Honse (calcprogrammer1@gmail.com) 01 Dec 2023 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ #pragma once diff --git a/Controllers/PhilipsWizController/PhilipsWizController.cpp b/Controllers/PhilipsWizController/PhilipsWizController.cpp index aef1648a7..efb92fb69 100644 --- a/Controllers/PhilipsWizController/PhilipsWizController.cpp +++ b/Controllers/PhilipsWizController/PhilipsWizController.cpp @@ -175,12 +175,14 @@ void PhilipsWizController::ReceiveThreadFunction() { char recv_buf[1025]; + port.set_receive_timeout(1, 0); + while(ReceiveThreadRun.load()) { /*-----------------------------------------------------------------*\ | Receive up to 1024 bytes from the device with a 1s timeout | \*-----------------------------------------------------------------*/ - int size = port.udp_listen_timeout(recv_buf, 1024, 1, 0); + int size = port.udp_listen(recv_buf, 1024); if(size > 0) { diff --git a/net_port/net_port.cpp b/net_port/net_port.cpp index 70d560600..8810a5120 100644 --- a/net_port/net_port.cpp +++ b/net_port/net_port.cpp @@ -119,23 +119,17 @@ int net_port::udp_listen(char * recv_data, int length) return(recvfrom(sock, recv_data, length, 0, NULL, NULL)); } -int net_port::udp_listen_timeout(char * recv_data, int length, int sec, int usec) +void net_port::set_receive_timeout(int sec, int usec) { - fd_set fds; +#ifdef WIN32 + DWORD tv = ( sec * 1000 ) + ( usec / 1000 ); +#else struct timeval tv; - - FD_ZERO(&fds); - FD_SET(sock, &fds); - tv.tv_sec = sec; tv.tv_usec = usec; +#endif - if(select((int)sock, &fds, NULL, NULL, &tv) <= 0) - { - return(0); - } - - return(recvfrom(sock, recv_data, length, 0, NULL, NULL)); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv); } int net_port::udp_write(char * buffer, int length) diff --git a/net_port/net_port.h b/net_port/net_port.h index 2385eaa56..7f0ff4142 100644 --- a/net_port/net_port.h +++ b/net_port/net_port.h @@ -71,7 +71,6 @@ public: void udp_join_multicast_group(const char * group_name); int udp_listen(char * recv_data, int length); - int udp_listen_timeout(char * recv_data, int length, int sec, int usec); int tcp_listen(char * recv_data, int length); //Function to write data to the serial port @@ -81,6 +80,8 @@ public: void tcp_close(); + void set_receive_timeout(int sec, int usec); + bool connected; SOCKET sock;