Make broadcast receiver thread static so that all instances share it, fix receiver timeout issue

This commit is contained in:
Adam Honse
2025-05-16 00:45:00 -05:00
parent 3f4d87480b
commit cc7ef576fd
8 changed files with 272 additions and 159 deletions

View File

@@ -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 <nlohmann/json.hpp>
@@ -27,54 +30,46 @@ base64::byte CalculateXorChecksum(std::vector<base64::byte> 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*> GoveeController::callbacks;
std::thread* GoveeController::ReceiveThread;
std::atomic<bool> 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;
}
}
}

View File

@@ -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 <string>
#include <thread>
#include <vector>
#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<bool> 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<GoveeController*> callbacks;
static std::thread* ReceiveThread;
static std::atomic<bool> ReceiveThreadRun;
static void ReceiveBroadcastThreadFunction();
static void RegisterReceiveBroadcastCallback(GoveeController* controller_ptr);
static void UnregisterReceiveBroadcastCallback(GoveeController* controller_ptr);
};

View File

@@ -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 <stdio.h>
#include <stdlib.h>
#include <vector>
#include "Detector.h"
#include "GoveeController.h"
#include "RGBController.h"
#include "RGBController_Govee.h"
#include "SettingsManager.h"
#include <vector>
#include <stdio.h>
#include <stdlib.h>
/******************************************************************************************\
* *
@@ -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() */

View File

@@ -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 <map>
#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);
}
}

View File

@@ -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

View File

@@ -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)
{

View File

@@ -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)

View File

@@ -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;