Compare commits

...

6 Commits

Author SHA1 Message Date
76ccf20c30 Fixed UdpDeviceList Pick
Fixed UdpListener Start
2024-02-03 12:23:34 +01:00
a069133a68 Set Api Key (#4)
* Delete .gitignore

* Added Readme, Added Nuget Infos in Project

* Added GoveeService that unites Api and Udp Service

Added ColorTemp Method to Udp Service

* Added UdpListener check

* Added ApiKey check

* Moved Class and changed Namespace

* Update GoveeCSharpConnector.csproj

* Set Api Key on Request
2024-02-03 01:25:44 +01:00
bb01b3342d Version 1.1.0 (#3)
* Delete .gitignore

* Added Readme, Added Nuget Infos in Project

* Added GoveeService that unites Api and Udp Service

Added ColorTemp Method to Udp Service

* Added UdpListener check

* Added ApiKey check

* Moved Class and changed Namespace

* Update GoveeCSharpConnector.csproj
2024-02-03 00:57:33 +01:00
602926c43e Create nuget.yml
Add Nuget Action
2024-02-02 19:21:45 +01:00
2eef997c6e Update README.md (#2) 2024-02-02 19:06:51 +01:00
bb9364c491 Dev (#1)
* Delete .gitignore

* Added Readme, Added Nuget Infos in Project
2024-02-02 19:05:57 +01:00
13 changed files with 314 additions and 43 deletions

27
.github/workflows/nuget.yml vendored Normal file
View File

@ -0,0 +1,27 @@
name: Release to NuGet
on:
push:
branches:
- master # Default release branch
pull_request:
branches:
- master # Run the workflow for all pull requests on Branch Master
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v4.1.1
- name: Setup .NET SDK
uses: actions/setup-dotnet@v1.5
- name: Build
run: dotnet build -c Release
- name: Test
run: dotnet test -c Release --no-build
- name: Pack nugets
run: dotnet pack -c Release --no-build --output .
- name: Push to NuGet
run: dotnet nuget push "*.nupkg" --api-key ${{secrets.NUGET_KEY}} --source https://api.nuget.org/v3/index.json

View File

@ -1,13 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/contentModel.xml
/projectSettingsUpdater.xml
/.idea.GoveeCSharpConnector.iml
/modules.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -5,16 +5,21 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<LangVersion>latest</LangVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Title>CSharp Library to control Govee Lights via Govee Web Api and Lan Udp!</Title>
<Authors>Locxion</Authors>
<Description>CSharp Library to control Govee Lights via Govee Web Api and Lan Udp!</Description>
<PackageProjectUrl>https://github.com/Locxion/GoveeCSharpConnector</PackageProjectUrl>
<RepositoryUrl>https://github.com/Locxion/GoveeCSharpConnector</RepositoryUrl>
<PackageLicenseUrl>https://github.com/Locxion/GoveeCSharpConnector/blob/main/LICENSE</PackageLicenseUrl>
<Version>1.1.2</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Net.Http.Json" Version="8.0.0" />
<PackageReference Include="System.Reactive" Version="6.0.0" />
<PackageReference Include="System.Text.Json" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
</Project>

View File

@ -10,27 +10,32 @@ public interface IGoveeApiService
/// </summary>
/// <param name="apiKey">Api Key as String</param>
void SetApiKey(string apiKey);
/// <summary>
/// Returns current set Govee Api Key
/// </summary>
/// <returns>Govee Api Key as String</returns>
string GetApiKey();
/// <summary>
/// Removes the Set Api Key and resets the HTTP Header
/// </summary>
void RemoveApiKey();
/// <summary>
/// Requests all Devices registered to Api Key Govee Account
/// </summary>
/// <returns>List of GoveeApiDevices</returns>
Task<List<GoveeApiDevice>> GetDevices();
/// <summary>
/// Requests the State of a single Govee Device
/// </summary>
/// <param name="deviceId">Device Id Guid as string</param>
/// <param name="deviceModel">Device Model Number as string</param>
/// <returns>GoveeApiStat Object</returns>
public Task<GoveeApiState> GetDeviceState(string deviceId, string deviceModel);
Task<GoveeApiState> GetDeviceState(string deviceId, string deviceModel);
/// <summary>
/// Sets the On/Off state of a single Govee Device
/// </summary>
@ -38,7 +43,8 @@ public interface IGoveeApiService
/// <param name="deviceModel">Device Model Number as string</param>
/// <param name="on"></param>
/// <returns></returns>
public Task ToggleState(string deviceId, string deviceModel, bool on);
Task ToggleState(string deviceId, string deviceModel, bool on);
/// <summary>
/// Sets the Brightness in Percent of a single Govee Device
/// </summary>
@ -46,7 +52,8 @@ public interface IGoveeApiService
/// <param name="deviceModel">Device Model Number as string</param>
/// <param name="value">Brightness in Percent as Int</param>
/// <returns></returns>
public Task SetBrightness(string deviceId, string deviceModel, int value);
Task SetBrightness(string deviceId, string deviceModel, int value);
/// <summary>
/// Sets a Rgb Color of a single Govee Device
/// </summary>
@ -54,7 +61,8 @@ public interface IGoveeApiService
/// <param name="deviceModel">Device Model Number as string</param>
/// <param name="color">Rgb Color</param>
/// <returns></returns>
public Task SetColor(string deviceId, string deviceModel, RgbColor color);
Task SetColor(string deviceId, string deviceModel, RgbColor color);
/// <summary>
/// Sets the Color Temperature of a single Govee Device
/// </summary>
@ -62,6 +70,5 @@ public interface IGoveeApiService
/// <param name="deviceModel">Device Model Number as string</param>
/// <param name="value">Color Temp in Kelvin as Int</param>
/// <returns></returns>
public Task SetColorTemp(string deviceId, string deviceModel, int value);
Task SetColorTemp(string deviceId, string deviceModel, int value);
}

View File

@ -0,0 +1,62 @@
using GoveeCSharpConnector.Objects;
namespace GoveeCSharpConnector.Interfaces;
public interface IGoveeService
{
/// <summary>
/// Govee Api Key
/// </summary>
string GoveeApiKey { get; set; }
/// <summary>
/// Gets a List of Govee Devices
/// </summary>
/// <param name="onlyLan">If true returns that are available on Api and Lan</param>
/// <returns>List of Govee Devices</returns>
Task<List<GoveeDevice>> GetDevices(bool onlyLan = true);
/// <summary>
/// Gets the State of a GoveeDevice
/// </summary>
/// <param name="goveeDevice">GoveeDevice</param>
/// <param name="useUdp">Use Udp Connection instead of the Api</param>
/// <returns></returns>
Task<GoveeState> GetDeviceState(GoveeDevice goveeDevice, bool useUdp = true);
/// <summary>
/// Sets the On/Off State of the GoveeDevice
/// </summary>
/// <param name="goveeDevice">GoveeDevice</param>
/// <param name="on"></param>
/// <param name="useUdp">Use Udp Connection instead of the Api</param>
/// <returns></returns>
Task ToggleState(GoveeDevice goveeDevice, bool on, bool useUdp = true);
/// <summary>
/// Sets the Brightness of the GoveeDevice
/// </summary>
/// <param name="goveeDevice">GoveeDevice</param>
/// <param name="value">Brightness in Percent</param>
/// <param name="useUdp">Use Udp Connection instead of the Api</param>
/// <returns></returns>
Task SetBrightness(GoveeDevice goveeDevice, int value, bool useUdp = true);
/// <summary>
/// Sets the Color of the GoveeDevice
/// </summary>
/// <param name="goveeDevice">GoveeDevice</param>
/// <param name="color">RgBColor</param>
/// <param name="useUdp">Use Udp Connection instead of the Api</param>
/// <returns></returns>
Task SetColor(GoveeDevice goveeDevice, RgbColor color, bool useUdp = true);
/// <summary>
/// Sets the Color Temperature in Kelvin for the GoveeDevice
/// </summary>
/// <param name="goveeDevice">GoveeDevice</param>
/// <param name="value">Color Temp in Kelvin</param>
/// <param name="useUdp">Use Udp Connection instead of the Api</param>
/// <returns></returns>
Task SetColorTemp(GoveeDevice goveeDevice, int value, bool useUdp = true);
}

View File

@ -12,7 +12,7 @@ public interface IGoveeUdpService
/// <summary>
/// Sends a Scan Command via Udp Multicast.
/// </summary>
/// <param name="timeout">Standard 200ms</param>
/// <param name="timeout">Standard 250ms</param>
/// <returns>List of GoveeUdpDevices</returns>
Task<List<GoveeUdpDevice>> GetDevices(TimeSpan? timeout = null);
@ -21,7 +21,7 @@ public interface IGoveeUdpService
/// </summary>
/// <param name="deviceAddress">Ip Address of the Device</param>
/// <param name="uniCastPort">Port of the Device. Standard 4003</param>
/// <param name="timeout">Standard 200ms</param>
/// <param name="timeout">Standard 250ms</param>
/// <returns></returns>
Task<GoveeUdpState> GetState(string deviceAddress, int uniCastPort = 4003, TimeSpan? timeout = null);
@ -49,6 +49,15 @@ public interface IGoveeUdpService
/// <param name="uniCastPort">Port of the Device. Standard 4003</param>
/// <returns></returns>
Task SetColor(string deviceAddress, RgbColor color, int uniCastPort = 4003);
/// <summary>
/// Sets the ColorTemp of the Device
/// </summary>
/// <param name="deviceAddress">Ip Address of the Device</param>
/// <param name="colorTempInKelvin"></param>
/// <param name="uniCastPort">Port of the Device. Standard 4003</param>
/// <returns></returns>
Task SetColorTemp(string deviceAddress, int colorTempInKelvin, int uniCastPort = 4003);
/// <summary>
/// Starts the Udp Listener
/// </summary>

View File

@ -0,0 +1,9 @@
namespace GoveeCSharpConnector.Objects;
public class GoveeDevice
{
public string DeviceId { get; set; }
public string Model { get; set; }
public string DeviceName { get; set; }
public string Address { get; set; }
}

View File

@ -0,0 +1,11 @@
using GoveeCSharpConnector.Enums;
namespace GoveeCSharpConnector.Objects;
public class GoveeState
{
public PowerState State { get; set; }
public int Brightness { get; set; }
public RgbColor Color { get; set; }
public int ColorTempInKelvin { get; set; }
}

View File

@ -7,6 +7,6 @@ public class Properties
public bool Online { get; set; }
public PowerState PowerState { get; set; }
public int Brightness { get; set; }
public int? ColorTemp { get; set; }
public int ColorTemp { get; set; }
public RgbColor Color { get; set; }
}

View File

@ -0,0 +1,108 @@
using GoveeCSharpConnector.Interfaces;
using GoveeCSharpConnector.Objects;
namespace GoveeCSharpConnector.Services;
public class GoveeService : IGoveeService
{
public string GoveeApiKey { get; set; }
private readonly IGoveeApiService _apiService;
private readonly IGoveeUdpService _udpService;
public GoveeService(IGoveeApiService apiService,IGoveeUdpService udpService)
{
_apiService = apiService ?? throw new ArgumentNullException(nameof(apiService));
_udpService = udpService ?? throw new ArgumentNullException(nameof(udpService));
}
public async Task<List<GoveeDevice>> GetDevices(bool onlyLan = true)
{
if (string.IsNullOrWhiteSpace(GoveeApiKey)) throw new Exception("No Govee Api Key Set!");
_apiService.SetApiKey(GoveeApiKey);
var apiDevices = await _apiService.GetDevices();
var devices = apiDevices.Select(apiDevice => new GoveeDevice() { DeviceId = apiDevice.DeviceId, DeviceName = apiDevice.DeviceName, Model = apiDevice.Model, Address = "onlyAvailableOnUdpRequest" }).ToList();
if (!onlyLan)
return devices;
if (!_udpService.IsListening())
_udpService.StartUdpListener();
var udpDevices = await _udpService.GetDevices();
var combinedDevices = (from goveeDevice in devices let matchingDevice = udpDevices.FirstOrDefault(x => x.device == goveeDevice.DeviceId)
where matchingDevice is not null select
new GoveeDevice { DeviceId = goveeDevice.DeviceId, DeviceName = goveeDevice.DeviceName, Model = goveeDevice.Model, Address = matchingDevice.ip }).ToList();
return combinedDevices;
}
public async Task<GoveeState> GetDeviceState(GoveeDevice goveeDevice, bool useUdp = true)
{
if (useUdp)
{
if (!_udpService.IsListening())
_udpService.StartUdpListener();
if (string.IsNullOrWhiteSpace(goveeDevice.Address)) throw new Exception("Device not available via Udp/Lan");
var udpState = await _udpService.GetState(goveeDevice.Address);
return new GoveeState() { State = udpState.onOff, Brightness = udpState.brightness, Color = udpState.color, ColorTempInKelvin = udpState.colorTempInKelvin };
}
if (string.IsNullOrWhiteSpace(GoveeApiKey)) throw new Exception("No Govee Api Key Set!");
_apiService.SetApiKey(GoveeApiKey);
var apiState = await _apiService.GetDeviceState(goveeDevice.DeviceId, goveeDevice.Model);
return new GoveeState{State = apiState.Properties.PowerState, Brightness = apiState.Properties.Brightness, Color = apiState.Properties.Color, ColorTempInKelvin = apiState.Properties.ColorTemp};
}
public async Task ToggleState(GoveeDevice goveeDevice, bool on, bool useUdp = true)
{
if (useUdp)
{
if (string.IsNullOrWhiteSpace(goveeDevice.Address)) throw new Exception("Device not available via Udp/Lan");
await _udpService.ToggleDevice(goveeDevice.Address, on);
return;
}
if (string.IsNullOrWhiteSpace(GoveeApiKey)) throw new Exception("No Govee Api Key Set!");
_apiService.SetApiKey(GoveeApiKey);
await _apiService.ToggleState(goveeDevice.DeviceId, goveeDevice.Model, on);
}
public async Task SetBrightness(GoveeDevice goveeDevice, int value, bool useUdp = true)
{
if (useUdp)
{
if (string.IsNullOrWhiteSpace(goveeDevice.Address)) throw new Exception("Device not available via Udp/Lan");
await _udpService.SetBrightness(goveeDevice.Address, value);
return;
}
if (string.IsNullOrWhiteSpace(GoveeApiKey)) throw new Exception("No Govee Api Key Set!");
_apiService.SetApiKey(GoveeApiKey);
await _apiService.SetBrightness(goveeDevice.DeviceId, goveeDevice.Model, value);
}
public async Task SetColor(GoveeDevice goveeDevice, RgbColor color, bool useUdp = true)
{
if (useUdp)
{
if (string.IsNullOrWhiteSpace(goveeDevice.Address)) throw new Exception("Device not available via Udp/Lan");
await _udpService.SetColor(goveeDevice.Address, color);
return;
}
if (string.IsNullOrWhiteSpace(GoveeApiKey)) throw new Exception("No Govee Api Key Set!");
_apiService.SetApiKey(GoveeApiKey);
await _apiService.SetColor(goveeDevice.DeviceId, goveeDevice.Model, color);
}
public async Task SetColorTemp(GoveeDevice goveeDevice, int value, bool useUdp = true)
{
if (useUdp)
{
if (string.IsNullOrWhiteSpace(goveeDevice.Address)) throw new Exception("Device not available via Udp/Lan");
await _udpService.SetColorTemp(goveeDevice.Address, value);
return;
}
if (string.IsNullOrWhiteSpace(GoveeApiKey)) throw new Exception("No Govee Api Key Set!");
_apiService.SetApiKey(GoveeApiKey);
await _apiService.SetColorTemp(goveeDevice.DeviceId, goveeDevice.Model, value);
}
}

View File

@ -32,15 +32,17 @@ public class GoveeUdpService : IGoveeUdpService
/// <inheritdoc/>
public async Task<List<GoveeUdpDevice>> GetDevices(TimeSpan? timeout = null)
{
if (!_udpListenerActive)
throw new Exception("Udp Listener not started!");
// Block this Method until current call reaches end of Method
await _semaphore.WaitAsync();
try
{
// Build Message
var message = new GoveeUdpMessage()
var message = new GoveeUdpMessage
{
msg = new msg()
msg = new msg
{
cmd = "scan",
data = new { account_topic = "reserve" }
@ -48,7 +50,7 @@ public class GoveeUdpService : IGoveeUdpService
};
// Subscribe to ScanResultSubject
var devicesTask = _scanResultSubject
.TakeUntil(Observable.Timer(timeout ?? TimeSpan.FromMilliseconds(300)))
.TakeUntil(Observable.Timer(timeout ?? TimeSpan.FromMilliseconds(250)))
.ToList()
.ToTask();
@ -73,12 +75,14 @@ public class GoveeUdpService : IGoveeUdpService
/// <inheritdoc/>
public async Task<GoveeUdpState> GetState(string deviceAddress, int uniCastPort = 4003, TimeSpan? timeout = null)
{
if (!_udpListenerActive)
throw new Exception("Udp Listener not started!");
try
{
// Build Message
var message = new GoveeUdpMessage()
var message = new GoveeUdpMessage
{
msg = new msg()
msg = new msg
{
cmd = "devStatus",
data = new { }
@ -86,7 +90,7 @@ public class GoveeUdpService : IGoveeUdpService
};
// Subscribe to ScanResultSubject
var devicesTask = _stateResultSubject
.TakeUntil(Observable.Timer(timeout ?? TimeSpan.FromMilliseconds(200)))
.TakeUntil(Observable.Timer(timeout ?? TimeSpan.FromMilliseconds(250)))
.ToTask();
// Send Message
@ -107,9 +111,9 @@ public class GoveeUdpService : IGoveeUdpService
try
{
// Build Message
var message = new GoveeUdpMessage()
var message = new GoveeUdpMessage
{
msg = new msg()
msg = new msg
{
cmd = "turn",
data = new { value = on ? 1 : 0 }
@ -131,9 +135,9 @@ public class GoveeUdpService : IGoveeUdpService
try
{
// Build Message
var message = new GoveeUdpMessage()
var message = new GoveeUdpMessage
{
msg = new msg()
msg = new msg
{
cmd = "brightness",
data = new { value = brightness }
@ -155,9 +159,9 @@ public class GoveeUdpService : IGoveeUdpService
try
{
// Build Message
var message = new GoveeUdpMessage()
var message = new GoveeUdpMessage
{
msg = new msg()
msg = new msg
{
cmd = "colorwc",
data = new
@ -173,7 +177,6 @@ public class GoveeUdpService : IGoveeUdpService
};
// Send Message
SendUdpMessage(JsonSerializer.Serialize(message), deviceAddress, uniCastPort);
}
catch (Exception e)
{
@ -181,6 +184,39 @@ public class GoveeUdpService : IGoveeUdpService
throw;
}
}
public async Task SetColorTemp(string deviceAddress, int colorTempInKelvin, int uniCastPort = 4003)
{
try
{
// Build Message
var message = new GoveeUdpMessage
{
msg = new msg
{
cmd = "colorwc",
data = new
{
color = new
{
r = 0,
g = 0,
b = 0
},
colorTempInKelvin = colorTempInKelvin
}
}
};
// Send Message
SendUdpMessage(JsonSerializer.Serialize(message), deviceAddress, uniCastPort);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
/// <inheritdoc/>
public async void StartUdpListener()
{
@ -196,6 +232,8 @@ public class GoveeUdpService : IGoveeUdpService
public void StopUdpListener()
{
_udpListenerActive = false;
_udpClient.DropMulticastGroup(IPAddress.Parse(GoveeMulticastAddress));
_udpClient.Close();
}
private static void SendUdpMessage(string message, string receiverAddress, int receiverPort)
@ -217,12 +255,13 @@ public class GoveeUdpService : IGoveeUdpService
}
}
private void SetupUdpClientListener()
private async void SetupUdpClientListener()
{
_udpClient.ExclusiveAddressUse = false;
var localEndPoint = new IPEndPoint(IPAddress.Any, GoveeMulticastPortListen);
_udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
_udpClient.Client.Bind(localEndPoint);
await StartListener();
}
private async Task StartListener()
@ -245,10 +284,9 @@ public class GoveeUdpService : IGoveeUdpService
}
});
}
finally
catch(Exception ex)
{
_udpClient.DropMulticastGroup(IPAddress.Parse(GoveeMulticastAddress));
_udpClient.Close();
throw ex;
}
}

View File

@ -243,7 +243,7 @@ public class Program
return GetUdpDeviceSelection();
}
return _udpDevices[result];
return _udpDevices[result-1];
}
private static void HandleApiInput()

8
README.md Normal file
View File

@ -0,0 +1,8 @@
# GoveeCSharpConnector
![netStandard2.0](https://img.shields.io/badge/.NET%20Standard-2.0-blueviolet)
[![Nuget](https://img.shields.io/nuget/v/GoveeCSharpConnector?cacheSeconds=50)](https://www.nuget.org/packages/GoveeCSharpConnector/)
# About
Simple .net Library to interface with Govee Smart Lights via their Web Api or Lan Udp connection.