using System.Net; using System.Net.Sockets; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Reactive.Threading.Tasks; using System.Text; using System.Text.Json; using GoveeCSharpConnector.Interfaces; using GoveeCSharpConnector.Objects; namespace GoveeCSharpConnector.Services; public class GoveeUdpService : IGoveeUdpService { private const string GoveeMulticastAddress = "239.255.255.250"; private const int GoveeMulticastPortListen = 4002; private const int GoveeMulticastPortSend = 4001; private readonly UdpClient _udpClient = new(); private bool _udpListenerActive = true; private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly Subject _messageSubject = new(); private readonly Subject _scanResultSubject = new(); private readonly Subject _stateResultSubject = new(); public IObservable Messages => _messageSubject; public GoveeUdpService() { SetupUdpClientListener(); } /// public async Task> GetDevices(TimeSpan? timeout = null) { // Block this Method until current call reaches end of Method await _semaphore.WaitAsync(); try { // Build Message var message = new GoveeUdpMessage() { msg = new msg() { cmd = "scan", data = new { account_topic = "reserve" } } }; // Subscribe to ScanResultSubject var devicesTask = _scanResultSubject .TakeUntil(Observable.Timer(timeout ?? TimeSpan.FromMilliseconds(200))) .ToList() .ToTask(); // Send Message SendUdpMessage(JsonSerializer.Serialize(message), GoveeMulticastAddress, GoveeMulticastPortSend); // Return List return (await devicesTask).ToList(); } catch (Exception e) { Console.WriteLine(e); throw; } finally { // Release Method Block _semaphore.Release(); } } /// public async Task GetState(string deviceAddress, int uniCastPort = 4003, TimeSpan? timeout = null) { try { // Build Message var message = new GoveeUdpMessage() { msg = new msg() { cmd = "devStatus", data = new { } } }; // Subscribe to ScanResultSubject var devicesTask = _stateResultSubject .TakeUntil(Observable.Timer(timeout ?? TimeSpan.FromMilliseconds(200))) .ToTask(); // Send Message SendUdpMessage(JsonSerializer.Serialize(message), deviceAddress, uniCastPort); // Return state return await devicesTask; } catch (Exception e) { Console.WriteLine(e); throw; } } /// public async Task ToggleDevice(string deviceAddress, bool on, int uniCastPort = 4003) { try { // Build Message var message = new GoveeUdpMessage() { msg = new msg() { cmd = "turn", data = new { value = on ? 1 : 0 } } }; // Send Message SendUdpMessage(JsonSerializer.Serialize(message), deviceAddress, uniCastPort); } catch (Exception e) { Console.WriteLine(e); throw; } } /// public async Task SetBrightness(string deviceAddress, short brightness, int uniCastPort = 4003) { try { // Build Message var message = new GoveeUdpMessage() { msg = new msg() { cmd = "brightness", data = new { value = brightness } } }; // Send Message SendUdpMessage(JsonSerializer.Serialize(message), deviceAddress, uniCastPort); } catch (Exception e) { Console.WriteLine(e); throw; } } /// public async Task SetColor(string deviceAddress, RgbColor color, int uniCastPort = 4003) { try { // Build Message var message = new GoveeUdpMessage() { msg = new msg() { cmd = "colorwc", data = new { color = new { r = color.R, g = color.G, b = color.B }, colorTempInKelvin = 0 } } }; // Send Message SendUdpMessage(JsonSerializer.Serialize(message), deviceAddress, uniCastPort); } catch (Exception e) { Console.WriteLine(e); throw; } } /// public async void StartUdpListener() { _udpListenerActive = true; await StartListener(); } /// public bool IsListening() { return _udpListenerActive; } /// public void StopUdpListener() { _udpListenerActive = false; } private static void SendUdpMessage(string message, string receiverAddress, int receiverPort) { var client = new UdpClient(); try { byte[] data = Encoding.UTF8.GetBytes(message); client.Send(data, data.Length, receiverAddress, receiverPort); } catch (Exception e) { Console.WriteLine(e); throw; } finally { client.Close(); } } private void SetupUdpClientListener() { _udpClient.ExclusiveAddressUse = false; var localEndPoint = new IPEndPoint(IPAddress.Any, GoveeMulticastPortListen); _udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); _udpClient.Client.Bind(localEndPoint); } private async Task StartListener() { try { _udpClient.JoinMulticastGroup(IPAddress.Parse(GoveeMulticastAddress)); Task.Run(async () => { while (_udpListenerActive) { var remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); var data = _udpClient.Receive(ref remoteEndPoint); var message = Encoding.UTF8.GetString(data); UdPMessageReceived(message); _messageSubject.OnNext(message); } }); } finally { _udpClient.DropMulticastGroup(IPAddress.Parse(GoveeMulticastAddress)); _udpClient.Close(); } } private void UdPMessageReceived(string message) { var response = JsonSerializer.Deserialize(message); switch (response.msg.cmd) { case "scan": var device = JsonSerializer.Deserialize(response.msg.data.ToString()); _scanResultSubject.OnNext(device); break; case "devStatus": var state = JsonSerializer.Deserialize(response.msg.data.ToString()); _stateResultSubject.OnNext(state); break; } } }