using GoveeCSharpConnector.Interfaces; using GoveeCSharpConnector.Objects; 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; namespace GoveeCSharpConnector.Services; public class GoveeUdpService : IGoveeUdpService { private bool _UDPListenerActive = true; private readonly UdpClient _UDPClient = new(); private const int _GoveeMulticastPortSend = 4001; private const int _GoveeMulticastPortListen = 4002; private const string _GoveeMulticastAddress = "239.255.255.250"; private readonly Subject _MessageSubject = new(); private readonly SemaphoreSlim _SemaphoreSlim = new(1, 1); private readonly Subject _ScanResultSubject = new(); private readonly Subject _StateResultSubject = new(); public bool IsListening() => _UDPListenerActive; public GoveeUdpService() => SetupUdpClientListenerAsync(); public IObservable Messages => _MessageSubject; public Task> GetDevicesAsync(TimeSpan? timeout = null) { if (!_UDPListenerActive) { throw new Exception("Udp Listener not started!"); } // Block this Method until current call reaches end of Method _SemaphoreSlim.Wait(); try { // Build Message GoveeUdpMessage message = new() { Msg = new Msg { Cmd = "scan", Data = new { account_topic = "reserve" } } }; // Subscribe to ScanResultSubject Task> devicesTask = _ScanResultSubject .TakeUntil(Observable.Timer(timeout ?? TimeSpan.FromMilliseconds(250))) .ToList() .ToTask(); // Send Message SendUdpMessage(JsonSerializer.Serialize(message), _GoveeMulticastAddress, _GoveeMulticastPortSend); // Return List return devicesTask; } catch (Exception e) { Console.WriteLine(e); throw; } finally { // Release Method Block _ = _SemaphoreSlim.Release(); } } public Task GetStateAsync(string deviceAddress, int uniCastPort = 4003, TimeSpan? timeout = null) { if (!_UDPListenerActive) { throw new Exception("Udp Listener not started!"); } // Build Message GoveeUdpMessage message = new() { Msg = new Msg { Cmd = "devStatus", Data = new { } } }; // Subscribe to ScanResultSubject Task devicesTask = _StateResultSubject .TakeUntil(Observable.Timer(timeout ?? TimeSpan.FromMilliseconds(250))) .ToTask(); // Send Message SendUdpMessage(JsonSerializer.Serialize(message), deviceAddress, uniCastPort); // Return state return devicesTask; } public void ToggleDevice(string deviceAddress, bool on, int uniCastPort = 4003) { // Build Message GoveeUdpMessage message = new() { Msg = new Msg { Cmd = "turn", Data = new { value = on ? 1 : 0 } } }; // Send Message SendUdpMessage(JsonSerializer.Serialize(message), deviceAddress, uniCastPort); } public void SetBrightness(string deviceAddress, int brightness, int uniCastPort = 4003) { // Build Message GoveeUdpMessage message = new() { Msg = new Msg { Cmd = "brightness", Data = new { value = brightness } } }; // Send Message SendUdpMessage(JsonSerializer.Serialize(message), deviceAddress, uniCastPort); } public void SetColor(string deviceAddress, RgbColor color, int uniCastPort = 4003) { // Build Message GoveeUdpMessage message = new() { 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); } public void SetColorTemp(string deviceAddress, int colorTempInKelvin, int uniCastPort = 4003) { // Build Message GoveeUdpMessage message = new() { Msg = new Msg { Cmd = "colorwc", Data = new { color = new { r = 0, g = 0, b = 0 }, colorTempInKelvin } } }; // Send Message SendUdpMessage(JsonSerializer.Serialize(message), deviceAddress, uniCastPort); } public Task StartUdpListenerAsync() { _UDPListenerActive = true; return StartListenerAsync(); } public void StopUdpListener() { _UDPListenerActive = false; _UDPClient.DropMulticastGroup(IPAddress.Parse(_GoveeMulticastAddress)); _UDPClient.Close(); } private static void SendUdpMessage(string message, string receiverAddress, int receiverPort) { UdpClient client = new(); try { byte[] data = Encoding.UTF8.GetBytes(message); Task task = client.SendAsync(data, data.Length, receiverAddress, receiverPort); task.Wait(); } catch (Exception) { throw; } finally { client.Close(); } } private Task SetupUdpClientListenerAsync() { _UDPClient.ExclusiveAddressUse = false; IPEndPoint localEndPoint = new(IPAddress.Any, _GoveeMulticastPortListen); _UDPClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); _UDPClient.Client.Bind(localEndPoint); return StartListenerAsync(); } private Task StartListenerAsync() { _UDPClient.JoinMulticastGroup(IPAddress.Parse(_GoveeMulticastAddress)); return StartListenerTask(); } private Task StartListenerTask() { while (_UDPListenerActive) { IPEndPoint remoteEndPoint = new(IPAddress.Any, 0); byte[] data = _UDPClient.Receive(ref remoteEndPoint); string message = Encoding.UTF8.GetString(data); UdPMessageReceived(message); _MessageSubject.OnNext(message); } return Task.CompletedTask; } private void UdPMessageReceived(string message) { GoveeUdpMessage response = JsonSerializer.Deserialize(message); switch (response.Msg.Cmd) { case "scan": GoveeUdpDevice device = JsonSerializer.Deserialize(response.Msg.Data.ToString()); _ScanResultSubject.OnNext(device); break; case "devStatus": GoveeUdpState state = JsonSerializer.Deserialize(response.Msg.Data.ToString()); _StateResultSubject.OnNext(state); break; } } }