Files
govee-csharp-connector/GoveeCSharpConnector/Services/GoveeUdpService.cs

213 lines
7.3 KiB
C#

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<string> _MessageSubject = new();
private readonly SemaphoreSlim _SemaphoreSlim = new(1, 1);
private readonly Subject<GoveeUdpDevice> _ScanResultSubject = new();
private readonly Subject<GoveeUdpState> _StateResultSubject = new();
public bool IsListening() =>
_UDPListenerActive;
public GoveeUdpService() =>
SetupUdpClientListenerAsync();
public IObservable<string> Messages =>
_MessageSubject;
public Task<IList<GoveeUdpDevice>> 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<IList<GoveeUdpDevice>> 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<GoveeUdpState> 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<GoveeUdpState> 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<int> 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<GoveeUdpMessage>(message);
switch (response.Msg.Cmd) {
case "scan":
GoveeUdpDevice device = JsonSerializer.Deserialize<GoveeUdpDevice>(response.Msg.Data.ToString());
_ScanResultSubject.OnNext(device);
break;
case "devStatus":
GoveeUdpState state = JsonSerializer.Deserialize<GoveeUdpState>(response.Msg.Data.ToString());
_StateResultSubject.OnNext(state);
break;
}
}
}