From efe6863b1484e3ce1e2172a86f5f13c25855144d Mon Sep 17 00:00:00 2001 From: Locxion Date: Thu, 1 Feb 2024 21:43:03 +0100 Subject: [PATCH] Init --- .gitignore | 5 + GoveeCSharpConnector.sln | 22 ++ GoveeCSharpConnector.sln.DotSettings | 2 + GoveeCSharpConnector/Enums/PowerState.cs | 7 + .../GoveeCSharpConnector.csproj | 20 ++ .../Interfaces/IGoveeApiService.cs | 67 +++++ .../Interfaces/IGoveeUdpService.cs | 63 ++++ GoveeCSharpConnector/Objects/ApiResponse.cs | 7 + GoveeCSharpConnector/Objects/Data.cs | 8 + .../Objects/GoveeApiCommand.cs | 14 + .../Objects/GoveeApiDevice.cs | 17 ++ GoveeCSharpConnector/Objects/GoveeApiState.cs | 16 ++ GoveeCSharpConnector/Objects/GoveeResponse.cs | 6 + .../Objects/GoveeUdpDevice.cs | 13 + .../Objects/GoveeUdpMessage.cs | 12 + GoveeCSharpConnector/Objects/GoveeUdpState.cs | 11 + GoveeCSharpConnector/Objects/Properties.cs | 12 + GoveeCSharpConnector/Objects/RgbColor.cs | 15 + .../Services/GoveeApiService.cs | 82 ++++++ .../Services/GoveeUdpService.cs | 270 ++++++++++++++++++ .../GoveeCsharpConnector.Example.csproj | 14 + GoveeCsharpConnector.Example/Program.cs | 202 +++++++++++++ 22 files changed, 885 insertions(+) create mode 100644 .gitignore create mode 100644 GoveeCSharpConnector.sln create mode 100644 GoveeCSharpConnector.sln.DotSettings create mode 100644 GoveeCSharpConnector/Enums/PowerState.cs create mode 100644 GoveeCSharpConnector/GoveeCSharpConnector.csproj create mode 100644 GoveeCSharpConnector/Interfaces/IGoveeApiService.cs create mode 100644 GoveeCSharpConnector/Interfaces/IGoveeUdpService.cs create mode 100644 GoveeCSharpConnector/Objects/ApiResponse.cs create mode 100644 GoveeCSharpConnector/Objects/Data.cs create mode 100644 GoveeCSharpConnector/Objects/GoveeApiCommand.cs create mode 100644 GoveeCSharpConnector/Objects/GoveeApiDevice.cs create mode 100644 GoveeCSharpConnector/Objects/GoveeApiState.cs create mode 100644 GoveeCSharpConnector/Objects/GoveeResponse.cs create mode 100644 GoveeCSharpConnector/Objects/GoveeUdpDevice.cs create mode 100644 GoveeCSharpConnector/Objects/GoveeUdpMessage.cs create mode 100644 GoveeCSharpConnector/Objects/GoveeUdpState.cs create mode 100644 GoveeCSharpConnector/Objects/Properties.cs create mode 100644 GoveeCSharpConnector/Objects/RgbColor.cs create mode 100644 GoveeCSharpConnector/Services/GoveeApiService.cs create mode 100644 GoveeCSharpConnector/Services/GoveeUdpService.cs create mode 100644 GoveeCsharpConnector.Example/GoveeCsharpConnector.Example.csproj create mode 100644 GoveeCsharpConnector.Example/Program.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..add57be --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ \ No newline at end of file diff --git a/GoveeCSharpConnector.sln b/GoveeCSharpConnector.sln new file mode 100644 index 0000000..a2ab44f --- /dev/null +++ b/GoveeCSharpConnector.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GoveeCSharpConnector", "GoveeCSharpConnector\GoveeCSharpConnector.csproj", "{EDF67B3A-9EBF-4C76-92E2-0AACD6B8081B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GoveeCsharpConnector.Example", "GoveeCsharpConnector.Example\GoveeCsharpConnector.Example.csproj", "{E487B84B-F619-430A-A0D3-80D44FE9EE3F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EDF67B3A-9EBF-4C76-92E2-0AACD6B8081B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDF67B3A-9EBF-4C76-92E2-0AACD6B8081B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDF67B3A-9EBF-4C76-92E2-0AACD6B8081B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDF67B3A-9EBF-4C76-92E2-0AACD6B8081B}.Release|Any CPU.Build.0 = Release|Any CPU + {E487B84B-F619-430A-A0D3-80D44FE9EE3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E487B84B-F619-430A-A0D3-80D44FE9EE3F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E487B84B-F619-430A-A0D3-80D44FE9EE3F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E487B84B-F619-430A-A0D3-80D44FE9EE3F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/GoveeCSharpConnector.sln.DotSettings b/GoveeCSharpConnector.sln.DotSettings new file mode 100644 index 0000000..da83716 --- /dev/null +++ b/GoveeCSharpConnector.sln.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/GoveeCSharpConnector/Enums/PowerState.cs b/GoveeCSharpConnector/Enums/PowerState.cs new file mode 100644 index 0000000..55ef694 --- /dev/null +++ b/GoveeCSharpConnector/Enums/PowerState.cs @@ -0,0 +1,7 @@ +namespace GoveeCSharpConnector.Enums; + +public enum PowerState +{ + Off = 0, + On = 1 +} \ No newline at end of file diff --git a/GoveeCSharpConnector/GoveeCSharpConnector.csproj b/GoveeCSharpConnector/GoveeCSharpConnector.csproj new file mode 100644 index 0000000..fa74176 --- /dev/null +++ b/GoveeCSharpConnector/GoveeCSharpConnector.csproj @@ -0,0 +1,20 @@ + + + + netstandard2.0 + enable + disable + latest + + + + + + + + + + + + + \ No newline at end of file diff --git a/GoveeCSharpConnector/Interfaces/IGoveeApiService.cs b/GoveeCSharpConnector/Interfaces/IGoveeApiService.cs new file mode 100644 index 0000000..5d4611e --- /dev/null +++ b/GoveeCSharpConnector/Interfaces/IGoveeApiService.cs @@ -0,0 +1,67 @@ +using GoveeCSharpConnector.Objects; + +namespace GoveeCSharpConnector.Interfaces; + +public interface IGoveeApiService +{ + /// + /// Sets the required Api Key for the Govee Api. + /// Request Api Key in the Mobile Phone App. + /// + /// Api Key as String + void SetApiKey(string apiKey); + /// + /// Returns current set Govee Api Key + /// + /// Govee Api Key as String + string GetApiKey(); + /// + /// Removes the Set Api Key and resets the HTTP Header + /// + void RemoveApiKey(); + /// + /// Requests all Devices registered to Api Key Govee Account + /// + /// List of GoveeApiDevices + Task> GetDevices(); + /// + /// Requests the State of a single Govee Device + /// + /// Device Id Guid as string + /// Device Model Number as string + /// GoveeApiStat Object + public Task GetDeviceState(string deviceId, string deviceModel); + /// + /// Sets the On/Off state of a single Govee Device + /// + /// Device Id Guid as string + /// Device Model Number as string + /// + /// + public Task ToggleState(string deviceId, string deviceModel, bool on); + /// + /// Sets the Brightness in Percent of a single Govee Device + /// + /// Device Id Guid as string + /// Device Model Number as string + /// Brightness in Percent as Int + /// + public Task SetBrightness(string deviceId, string deviceModel, int value); + /// + /// Sets a Rgb Color of a single Govee Device + /// + /// Device Id Guid as string + /// Device Model Number as string + /// Rgb Color + /// + public Task SetColor(string deviceId, string deviceModel, RgbColor color); + /// + /// Sets the Color Temperature of a single Govee Device + /// + /// Device Id Guid as string + /// Device Model Number as string + /// Color Temp in Kelvin as Int + /// + public Task SetColorTemp(string deviceId, string deviceModel, int value); + +} \ No newline at end of file diff --git a/GoveeCSharpConnector/Interfaces/IGoveeUdpService.cs b/GoveeCSharpConnector/Interfaces/IGoveeUdpService.cs new file mode 100644 index 0000000..cca79f0 --- /dev/null +++ b/GoveeCSharpConnector/Interfaces/IGoveeUdpService.cs @@ -0,0 +1,63 @@ +using System.Data; +using GoveeCSharpConnector.Objects; + +namespace GoveeCSharpConnector.Interfaces; + +public interface IGoveeUdpService +{ + /// + /// Sends a Scan Command via Udp Multicast. + /// + /// Standard 200ms + /// List of GoveeUdpDevices + Task> GetDevices(TimeSpan? timeout = null); + + /// + /// Request the State of the Device + /// + /// Ip Address of the Device + /// Port of the Device. Standard 4003 + /// Standard 200ms + /// + Task GetState(string deviceAddress, int uniCastPort = 4003, TimeSpan? timeout = null); + + /// + /// Sets the On/Off State of the Device + /// + /// Ip Address of the Device + /// + /// Port of the Device. Standard 4003 + /// + Task ToggleDevice(string deviceAddress, bool on, int uniCastPort = 4003); + /// + /// Sets the Brightness of the Device + /// + /// Ip Address of the Device + /// In Percent 1-100 + /// Port of the Device. Standard 4003 + /// + Task SetBrightness(string deviceAddress, short brightness, int uniCastPort = 4003); + /// + /// Sets the Color of the Device + /// + /// Ip Address of the Device + /// + /// Port of the Device. Standard 4003 + /// + Task SetColor(string deviceAddress, RgbColor color, int uniCastPort = 4003); + /// + /// Starts the Udp Listener + /// + /// + void StartUdpListener(); + /// + /// Returns the State of the Udp Listener + /// + /// True if Active + bool IsListening(); + /// + /// Stops the Udp Listener + /// + /// + void StopUdpListener(); +} \ No newline at end of file diff --git a/GoveeCSharpConnector/Objects/ApiResponse.cs b/GoveeCSharpConnector/Objects/ApiResponse.cs new file mode 100644 index 0000000..5639aab --- /dev/null +++ b/GoveeCSharpConnector/Objects/ApiResponse.cs @@ -0,0 +1,7 @@ +namespace GoveeCSharpConnector.Objects; + +public class ApiResponse +{ + public string? Message { get; set; } + public int Code { get; set; } +} \ No newline at end of file diff --git a/GoveeCSharpConnector/Objects/Data.cs b/GoveeCSharpConnector/Objects/Data.cs new file mode 100644 index 0000000..044a900 --- /dev/null +++ b/GoveeCSharpConnector/Objects/Data.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace GoveeCSharpConnector.Objects; + +public class Data +{ + public List Devices { get; set; } +} \ No newline at end of file diff --git a/GoveeCSharpConnector/Objects/GoveeApiCommand.cs b/GoveeCSharpConnector/Objects/GoveeApiCommand.cs new file mode 100644 index 0000000..d364ccd --- /dev/null +++ b/GoveeCSharpConnector/Objects/GoveeApiCommand.cs @@ -0,0 +1,14 @@ +namespace GoveeCSharpConnector.Objects; + +public class GoveeApiCommand +{ + public string Device { get; set; } + public string Model { get; set; } + public Command Cmd { get; set; } +} + +public class Command +{ + public string Name { get; set; } + public object Value { get; set; } +} \ No newline at end of file diff --git a/GoveeCSharpConnector/Objects/GoveeApiDevice.cs b/GoveeCSharpConnector/Objects/GoveeApiDevice.cs new file mode 100644 index 0000000..19e2c24 --- /dev/null +++ b/GoveeCSharpConnector/Objects/GoveeApiDevice.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace GoveeCSharpConnector.Objects; + +public class GoveeApiDevice +{ + [JsonPropertyName("device")] + public string DeviceId { get; set; } + public string Model { get; set; } + public string DeviceName { get; set; } + public bool Controllable { get; set; } + public bool Retrievable { get; set; } + [JsonPropertyName("supportCmds")] + public List SupportedCommands { get; set; } + public Properties Properties { get; set; } +} \ No newline at end of file diff --git a/GoveeCSharpConnector/Objects/GoveeApiState.cs b/GoveeCSharpConnector/Objects/GoveeApiState.cs new file mode 100644 index 0000000..586439e --- /dev/null +++ b/GoveeCSharpConnector/Objects/GoveeApiState.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace GoveeCSharpConnector.Objects; + +public class GoveeApiState +{ + [JsonPropertyName("device")] + public string DeviceId { get; set; } + + public string Model { get; set; } + + public string Name { get; set; } + + [JsonIgnore] + public Properties Properties { get; set; } +} \ No newline at end of file diff --git a/GoveeCSharpConnector/Objects/GoveeResponse.cs b/GoveeCSharpConnector/Objects/GoveeResponse.cs new file mode 100644 index 0000000..e9ad04f --- /dev/null +++ b/GoveeCSharpConnector/Objects/GoveeResponse.cs @@ -0,0 +1,6 @@ +namespace GoveeCSharpConnector.Objects; + +public class GoveeResponse : ApiResponse +{ + public Data Data { get; set; } +} \ No newline at end of file diff --git a/GoveeCSharpConnector/Objects/GoveeUdpDevice.cs b/GoveeCSharpConnector/Objects/GoveeUdpDevice.cs new file mode 100644 index 0000000..ea8935d --- /dev/null +++ b/GoveeCSharpConnector/Objects/GoveeUdpDevice.cs @@ -0,0 +1,13 @@ +// ReSharper disable InconsistentNaming +namespace GoveeCSharpConnector.Objects; + +public class GoveeUdpDevice +{ + public string ip { get; set; } + public string device { get; set; } + public string sku { get; set; } + public string bleVersionHard { get; set; } + public string bleVersionSoft { get; set; } + public string wifiVersionHard { get; set; } + public string wifiVersionSoft { get; set; } +} \ No newline at end of file diff --git a/GoveeCSharpConnector/Objects/GoveeUdpMessage.cs b/GoveeCSharpConnector/Objects/GoveeUdpMessage.cs new file mode 100644 index 0000000..6cd3800 --- /dev/null +++ b/GoveeCSharpConnector/Objects/GoveeUdpMessage.cs @@ -0,0 +1,12 @@ +// ReSharper disable InconsistentNaming +namespace GoveeCSharpConnector.Objects; + +public class GoveeUdpMessage +{ + public msg msg { get; set; } +} +public class msg +{ + public string cmd { get; set; } + public object data { get; set; } +} \ No newline at end of file diff --git a/GoveeCSharpConnector/Objects/GoveeUdpState.cs b/GoveeCSharpConnector/Objects/GoveeUdpState.cs new file mode 100644 index 0000000..8b9f4fd --- /dev/null +++ b/GoveeCSharpConnector/Objects/GoveeUdpState.cs @@ -0,0 +1,11 @@ +using GoveeCSharpConnector.Enums; + +namespace GoveeCSharpConnector.Objects; + +public class GoveeUdpState +{ + public PowerState onOff { get; set; } + public short brightness { get; set; } + public RgbColor color { get; set; } + public int colorTempInKelvin { get; set; } +} \ No newline at end of file diff --git a/GoveeCSharpConnector/Objects/Properties.cs b/GoveeCSharpConnector/Objects/Properties.cs new file mode 100644 index 0000000..1d23366 --- /dev/null +++ b/GoveeCSharpConnector/Objects/Properties.cs @@ -0,0 +1,12 @@ +using GoveeCSharpConnector.Enums; + +namespace GoveeCSharpConnector.Objects; + +public class Properties +{ + public bool Online { get; set; } + public PowerState PowerState { get; set; } + public int Brightness { get; set; } + public int? ColorTemp { get; set; } + public RgbColor Color { get; set; } +} \ No newline at end of file diff --git a/GoveeCSharpConnector/Objects/RgbColor.cs b/GoveeCSharpConnector/Objects/RgbColor.cs new file mode 100644 index 0000000..a079c54 --- /dev/null +++ b/GoveeCSharpConnector/Objects/RgbColor.cs @@ -0,0 +1,15 @@ +namespace GoveeCSharpConnector.Objects; + +public class RgbColor +{ + public short R { get; set; } + public short G { get; set; } + public short B { get; set; } + + public RgbColor(int r, int g, int b) + { + R = Convert.ToInt16(r); + G = Convert.ToInt16(g); + B = Convert.ToInt16(b); + } +} \ No newline at end of file diff --git a/GoveeCSharpConnector/Services/GoveeApiService.cs b/GoveeCSharpConnector/Services/GoveeApiService.cs new file mode 100644 index 0000000..a16c8f7 --- /dev/null +++ b/GoveeCSharpConnector/Services/GoveeApiService.cs @@ -0,0 +1,82 @@ +using System.Net.Http.Json; +using System.Text; +using System.Text.Json; +using GoveeCSharpConnector.Interfaces; +using GoveeCSharpConnector.Objects; + +namespace GoveeCSharpConnector.Services; + +public class GoveeApiService : IGoveeApiService +{ + private string _apiKey = string.Empty; + private const string GoveeApiAddress = "https://developer-api.govee.com/v1"; + private readonly HttpClient _httpClient = new(); + + /// + public void SetApiKey(string apiKey) + { + _apiKey = apiKey; + _httpClient.DefaultRequestHeaders.Add("Govee-API-Key", _apiKey); + } + /// + public string GetApiKey() + { + return _apiKey; + } + /// + public void RemoveApiKey() + { + _apiKey = string.Empty; + _httpClient.DefaultRequestHeaders.Remove("Govee-Api-Key"); + } + /// + public async Task> GetDevices() + { + var response = await _httpClient.GetFromJsonAsync($"{GoveeApiAddress}/devices"); + + return response.Data.Devices; + } + /// + public async Task GetDeviceState(string deviceId, string deviceModel) + { + return await _httpClient.GetFromJsonAsync($"{GoveeApiAddress}/devices/state?device={deviceId}&model={deviceModel}"); + } + /// + public async Task ToggleState(string deviceId, string deviceModel, bool on) + { + await SendCommand(deviceId, deviceModel, "turn", on ? "on" : "off"); + } + /// + public async Task SetBrightness(string deviceId, string deviceModel, int value) + { + await SendCommand(deviceId, deviceModel, "brightness", value); + } + /// + public async Task SetColor(string deviceId, string deviceModel, RgbColor color) + { + await SendCommand(deviceId, deviceModel, "color", color); + } + /// + public async Task SetColorTemp(string deviceId, string deviceModel, int value) + { + await SendCommand(deviceId, deviceModel, "colorTem", value); + } + + private async Task SendCommand(string deviceId, string deviceModel, string command, object commandObject) + { + var commandRequest = new GoveeApiCommand() + { + Device = deviceId, + Model = deviceModel, + Cmd = new Command() + { + Name = command, + Value = commandObject + } + }; + var httpContent = new StringContent(JsonSerializer.Serialize(commandRequest), Encoding.UTF8, "application/json"); + var response = await _httpClient.PutAsync($"{GoveeApiAddress}/devices/control", httpContent); + if (!response.IsSuccessStatusCode) + throw new Exception($"Govee Api Request failed. Status code: {response.StatusCode}, Message: {response.Content}"); + } +} \ No newline at end of file diff --git a/GoveeCSharpConnector/Services/GoveeUdpService.cs b/GoveeCSharpConnector/Services/GoveeUdpService.cs new file mode 100644 index 0000000..27d8b96 --- /dev/null +++ b/GoveeCSharpConnector/Services/GoveeUdpService.cs @@ -0,0 +1,270 @@ +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; + } + } +} \ No newline at end of file diff --git a/GoveeCsharpConnector.Example/GoveeCsharpConnector.Example.csproj b/GoveeCsharpConnector.Example/GoveeCsharpConnector.Example.csproj new file mode 100644 index 0000000..9366268 --- /dev/null +++ b/GoveeCsharpConnector.Example/GoveeCsharpConnector.Example.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/GoveeCsharpConnector.Example/Program.cs b/GoveeCsharpConnector.Example/Program.cs new file mode 100644 index 0000000..518c18d --- /dev/null +++ b/GoveeCsharpConnector.Example/Program.cs @@ -0,0 +1,202 @@ +using System.Net.Mime; +using System.Reflection; +using System.Xml.Linq; +using GoveeCSharpConnector.Objects; +using GoveeCSharpConnector.Services; + +namespace GoveeCsharpConnector.Example; + +public class Program +{ + private static GoveeApiService _goveeApiService = new GoveeApiService(); + public static List _apiDevices = new List(); + + public static async Task Main(string[] args) + { + while (true) + { + PrintWelcomeMessage(); + var input = Console.ReadLine(); + HandleKeyInput(input); + } + } + + private static async void HandleKeyInput(string input) + { + switch (input) + { + case "1": + HandleApiInput(); + EndSegment(); + break; + case "2": + Console.WriteLine("Requesting Devices ..."); + _apiDevices = await _goveeApiService.GetDevices(); + Console.WriteLine("Devices:"); + foreach (var device in _apiDevices) + { + Console.WriteLine($"Name: {device.DeviceName}, Device Id: {device.DeviceId}, Model: {device.Model}, Controllable {device.Controllable}"); + } + Console.WriteLine($"Total: {_apiDevices.Count} Devices."); + EndSegment(); + + break; + case "3": + if (_apiDevices.Count == 0) + { + Console.WriteLine("No Devices discovered! Please use Option 2 first!"); + EndSegment(); + return; + } + Console.WriteLine("Please enter the Name of the Device:"); + var nameInput = Console.ReadLine()?.ToLower(); + if (string.IsNullOrWhiteSpace(nameInput) || _apiDevices.FirstOrDefault(x => x.DeviceName.ToLower() == nameInput) is null) + { + Console.WriteLine("Device Name Invalid!"); + EndSegment(); + return; + } + + Console.WriteLine($"Do you want to turn the Device {nameInput} on or off?"); + var onOffInput = Console.ReadLine()?.ToLower(); + if (string.IsNullOrWhiteSpace(onOffInput) || (onOffInput != "on" && onOffInput != "off")) + { + Console.WriteLine("Invalid Input!"); + EndSegment(); + return; + } + + if (input == "on") + { + await _goveeApiService.ToggleState(_apiDevices.First(x => x.DeviceName.ToLower() == nameInput).DeviceId, _apiDevices.First(x => x.DeviceName.ToLower() == nameInput).Model, true); + } + else + { + await _goveeApiService.ToggleState(_apiDevices.First(x => x.DeviceName.ToLower() == nameInput).DeviceId, _apiDevices.First(x => x.DeviceName.ToLower() == nameInput).Model, false); + } + EndSegment(); + break; + case "4": + if (_apiDevices.Count == 0) + { + Console.WriteLine("No Devices discovered! Please use Option 2 first!"); + EndSegment(); + return; + } + Console.WriteLine("Please enter the Name of the Device:"); + var nameInput2 = Console.ReadLine()?.ToLower(); + if (string.IsNullOrWhiteSpace(nameInput2) || _apiDevices.FirstOrDefault(x => x.DeviceName.ToLower() == nameInput2) is null) + { + Console.WriteLine("Device Name Invalid!"); + EndSegment(); + return; + } + + Console.WriteLine($"Please enter a Brightness Value for Device {nameInput2}. 0-100"); + var brightnessInput = Console.ReadLine(); + int value = Convert.ToInt16(brightnessInput); + if (string.IsNullOrWhiteSpace(brightnessInput) || value < 0 || value > 100) + { + Console.WriteLine("Invalid Input!"); + EndSegment(); + return; + } + + await _goveeApiService.SetBrightness(_apiDevices.First(x => x.DeviceName.ToLower() == nameInput2).DeviceId, _apiDevices.First(x => x.DeviceName.ToLower() == nameInput2).Model, value); + Console.WriteLine($"Set Brightness of Device {nameInput2} to {value}%!"); + EndSegment(); + break; + case "5": + if (_apiDevices.Count == 0) + { + Console.WriteLine("No Devices discovered! Please use Option 2 first!"); + EndSegment(); + return; + } + Console.WriteLine("Please enter the Name of the Device:"); + var nameInput3 = Console.ReadLine()?.ToLower(); + if (string.IsNullOrWhiteSpace(nameInput3) || _apiDevices.FirstOrDefault(x => x.DeviceName.ToLower() == nameInput3) is null) + { + Console.WriteLine("Device Name Invalid!"); + EndSegment(); + return; + } + Console.WriteLine($"Please choose a Color to set {nameInput3} to ... (blue, red, green)"); + var colorInput = Console.ReadLine()?.ToLower(); + if (string.IsNullOrWhiteSpace(colorInput) || colorInput != "blue" || colorInput != "green" || colorInput != "red") + { + Console.WriteLine("Invalid Input!"); + EndSegment(); + return; + } + + var model = _apiDevices.FirstOrDefault(x => x.DeviceName.ToLower()== nameInput3)?.Model; + switch (colorInput) + { + case "blue": + await _goveeApiService.SetColor(_apiDevices.First(x => x.DeviceName.ToLower() == nameInput3).DeviceId, model, new RgbColor(0, 0 ,254)); + break; + case "green": + await _goveeApiService.SetColor(_apiDevices.First(x => x.DeviceName.ToLower() == nameInput3).DeviceId, model, new RgbColor(0, 254 ,0)); + break; + case "red": + await _goveeApiService.SetColor(_apiDevices.First(x => x.DeviceName.ToLower() == nameInput3).DeviceId, model, new RgbColor(254, 0 ,0)); + break; + } + Console.WriteLine($"Set Color of Device {nameInput3} to {colorInput}!"); + EndSegment(); + break; + } + } + + private static void HandleApiInput() + { + while (true) + { + Console.WriteLine("Please enter/paste your Govee Api Key ..."); + Console.WriteLine("Your Api Key should look something like this: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"); + var input = Console.ReadLine(); + if (input is null || input.Length != 36) + { + Console.WriteLine("Wrong Api Key Format!"); + continue; + } + _goveeApiService.SetApiKey(input); + break; + } + Console.WriteLine("Api Key saved!"); + } + + private static void EndSegment() + { + Console.WriteLine("---------------------------Press any Key to continue---------------------------"); + Console.ReadLine(); + } + + private static void PrintWelcomeMessage() + { + Console.WriteLine(); + Console.WriteLine("Welcome to the GoveeCSharpConnector Example!"); + Console.WriteLine($"Version: {Assembly.GetEntryAssembly()?.GetName().Version}"); + Console.WriteLine($"To test/explore the GoveeCSharpConnector Version: {Assembly.Load("GoveeCSharpConnector").GetName().Version}"); + Console.WriteLine("----------------------------------------------------------"); + if (string.IsNullOrEmpty(_goveeApiService.GetApiKey())) + { + Console.WriteLine("1 - Enter GoveeApi Key - START HERE (Required for Api Service Options!)"); + } + else + { + Console.WriteLine("1 - Enter GoveeApi Key - Already Set!"); + Console.WriteLine("Api Service:"); + Console.WriteLine("2 - Get a List of all Devices connected to the Api Key Account"); + Console.WriteLine("3 - Turn Device On or Off"); + Console.WriteLine("4 - Set Brightness for Device"); + Console.WriteLine("5 - Set Color of Device"); + } + Console.WriteLine("Udp Service - No Api Key needed!"); + Console.WriteLine("6 - Get a List of all Devices available in the Network"); + Console.WriteLine("7 - Turn Device On or Off"); + Console.WriteLine("8 - Set Brightness for Device"); + Console.WriteLine("9 - Set Color of Device"); + } +} \ No newline at end of file