diff --git a/YeelightAPI.Console/YeelightAPI.Console.csproj b/YeelightAPI.Console/YeelightAPI.Console.csproj index 60a952b..ebd5669 100644 --- a/YeelightAPI.Console/YeelightAPI.Console.csproj +++ b/YeelightAPI.Console/YeelightAPI.Console.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net6.0 false diff --git a/YeelightAPI.Console/packages.config b/YeelightAPI.Console/packages.config index 43cb3fa..102f159 100644 --- a/YeelightAPI.Console/packages.config +++ b/YeelightAPI.Console/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/YeelightAPI.UnitTests/YeelightAPI.UnitTests.csproj b/YeelightAPI.UnitTests/YeelightAPI.UnitTests.csproj index 5851d68..3a9228e 100644 --- a/YeelightAPI.UnitTests/YeelightAPI.UnitTests.csproj +++ b/YeelightAPI.UnitTests/YeelightAPI.UnitTests.csproj @@ -1,21 +1,21 @@  - netcoreapp3.1 + net6.0 false - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/YeelightAPI/Core/Constants.cs b/YeelightAPI/Core/Constants.cs index 2ca11e9..7a6a774 100644 --- a/YeelightAPI/Core/Constants.cs +++ b/YeelightAPI/Core/Constants.cs @@ -20,6 +20,11 @@ internal static class Constants /// public const int DefaultTimeout = 5000; + /// + /// Default starting port when searching available ports for music mode + /// + public const int DefaultMusicModeStartingPort = 5000; + /// /// Line separator /// diff --git a/YeelightAPI/Core/MusicModeInformations.cs b/YeelightAPI/Core/MusicModeInformations.cs new file mode 100644 index 0000000..c65ff2c --- /dev/null +++ b/YeelightAPI/Core/MusicModeInformations.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace YeelightAPI.Core +{ + /// + /// Informations about device music mode + /// + public class MusicModeInformations + { + /// + /// The host name + /// + public string HostName { get; internal set; } + + /// + /// The used port + /// + public int Port { get; internal set; } + + /// + /// Music mode enabled or not ? + /// + public bool Enabled { get; internal set; } + + /// + /// High rate enabled or not ? + /// + public bool HighRateEnabled { get; set; } + } +} diff --git a/YeelightAPI/Core/NetworkHelper.cs b/YeelightAPI/Core/NetworkHelper.cs new file mode 100644 index 0000000..543335f --- /dev/null +++ b/YeelightAPI/Core/NetworkHelper.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Text; + +namespace YeelightAPI.Core +{ + /// + /// Helper for networking operations + /// + public static class NetworkHelper + { + /// + /// Get the current IP adress + /// + /// + public static string GetLocalIpAddress() + { + using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, 0)) + { + socket.Connect("8.8.8.8", 65530); + IPEndPoint endPoint = socket.LocalEndPoint as IPEndPoint; + return endPoint.Address.ToString(); + } + } + + /// + /// Get the next available port + /// + /// the port number to start searching from + /// + public static int GetNextAvailablePort(int startingPort = 0) + { + IPEndPoint[] endPoints; + List portArray = new List(); + + IPGlobalProperties properties = IPGlobalProperties.GetIPGlobalProperties(); + + //getting active connections + TcpConnectionInformation[] connections = properties.GetActiveTcpConnections(); + if (connections.Any(c => c.LocalEndPoint.Port >= startingPort)) + { + portArray.Add(connections.First(c => c.LocalEndPoint.Port >= startingPort).LocalEndPoint.Port); + } + + //getting active tcp listners + endPoints = properties.GetActiveTcpListeners(); + if (endPoints.Any(c => c.Port >= startingPort)) + { + portArray.Add(endPoints.First(c => c.Port >= startingPort).Port); + } + + //getting active udp listeners + endPoints = properties.GetActiveUdpListeners(); + if (endPoints.Any(c => c.Port >= startingPort)) + { + portArray.Add(endPoints.First(c => c.Port >= startingPort).Port); + } + + if (portArray.Count != 0) + { + portArray.Sort(); + return portArray.First(); + } + + throw new Exception("No Available Port"); + + } + } +} diff --git a/YeelightAPI/Device.IDeviceController.cs b/YeelightAPI/Device.IDeviceController.cs index c3d6ea3..16453e3 100644 --- a/YeelightAPI/Device.IDeviceController.cs +++ b/YeelightAPI/Device.IDeviceController.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Net.Sockets; using System.Threading.Tasks; using YeelightAPI.Core; @@ -335,23 +337,28 @@ public async Task StartColorFlow(ColorFlow flow) } /// - /// Starts the music mode + /// starts the music mode for all devices, with specified port (or automatic port chosing if is null) /// /// - /// + /// /// - public async Task StartMusicMode(string hostname = null, int port = 12345) + public async Task StartMusicMode(string hostname, int? startingPort) { //init new TCP socket if (string.IsNullOrWhiteSpace(hostname)) { - hostname = GetLocalIpAddress(); + hostname = NetworkHelper.GetLocalIpAddress(); } - var listener = new TcpListener(System.Net.IPAddress.Parse(hostname), port); + if (!startingPort.HasValue) + { + startingPort = NetworkHelper.GetNextAvailablePort(Constants.DefaultMusicModeStartingPort); + } + + var listener = new TcpListener(System.Net.IPAddress.Parse(hostname), startingPort.Value); listener.Start(); - List parameters = new List() { (int)MusicAction.On, hostname, port }; + List parameters = new List() { (int)MusicAction.On, hostname, startingPort.Value }; CommandResult> result = await ExecuteCommandWithResponse>( method: METHODS.SetMusicMode, @@ -359,7 +366,9 @@ public async Task StartMusicMode(string hostname = null, int port = 12345) if (result.IsOk()) { - this.IsMusicModeEnabled = true; + this.MusicMode.Enabled = true; + this.MusicMode.HostName = hostname; + this.MusicMode.Port = startingPort.Value; this.Disconnect(); var musicTcpClient = await listener.AcceptTcpClientAsync(); _tcpClient = musicTcpClient; @@ -368,6 +377,36 @@ public async Task StartMusicMode(string hostname = null, int port = 12345) return result.IsOk(); } + /// + /// starts the music mode for all device, with specified ports + /// + /// + /// + /// + /// + public Task StartMusicMode(string hostName, IEnumerable ports) + { + if (ports == null || ports.Count() != 1) + throw new InvalidDataException("ports collection must have exactly one element"); + + return StartMusicMode(hostName, ports.First()); + } + + /// + /// starts the music mode for all device, with possibility to specify a Func that will be called for reach device to determine its port + /// + /// + /// + /// + /// + public Task StartMusicMode(string hostName, Func portChooser) + { + if (portChooser == null) + throw new InvalidDataException("portChooser parameter cannot be null"); + + return StartMusicMode(hostName, portChooser.Invoke(this)); + } + /// /// Stops the color flow /// @@ -395,8 +434,7 @@ public async Task StopMusicMode() if (result.IsOk()) { //disables the music mode - this.IsMusicModeEnabled = false; - await DisableMusicModeAsync(); + this.MusicMode.Enabled = false; return true; } diff --git a/YeelightAPI/Device.cs b/YeelightAPI/Device.cs index 0ea2647..d9f167f 100644 --- a/YeelightAPI/Device.cs +++ b/YeelightAPI/Device.cs @@ -99,7 +99,16 @@ public bool IsConnected /// /// Indicate wether the music mode is enabled /// - public bool IsMusicModeEnabled { get; private set; } + [Obsolete("IsMusicModeEnabled will be removed in the next version, please use MusicMode property instead")] + public bool IsMusicModeEnabled + { + get => this.MusicMode.Enabled; + } + + /// + /// Give informations about the music mode + /// + public MusicModeInformations MusicMode { get; private set; } = new MusicModeInformations(); /// /// The model. @@ -247,9 +256,10 @@ public void ExecuteCommand(METHODS method, List parameters = null) /// public async Task> ExecuteCommandWithResponse(METHODS method, List parameters = null) { - if (IsMusicModeEnabled) + if (this.MusicMode.Enabled) { //music mode enabled, there will be no response, we should assume everything works + _tcpClient.Client.NoDelay = this.MusicMode.HighRateEnabled; //update here instead of "SetMusicMode" so it can be changed dynamically int uniqueId = GetUniqueIdForCommand(); ExecuteCommand(method, uniqueId, parameters); return new CommandResult() { Id = uniqueId, Error = null, IsMusicResponse = true }; @@ -324,7 +334,7 @@ internal async Task> ExecuteCommandWithResponse(METHODS meth internal async Task DisableMusicModeAsync() { _ = await Connect(); - IsMusicModeEnabled = false; + MusicMode.Enabled = false; } @@ -332,16 +342,6 @@ internal async Task DisableMusicModeAsync() #region PRIVATE METHODS - private static string GetLocalIpAddress() - { - using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, 0)) - { - socket.Connect("8.8.8.8", 65530); - IPEndPoint endPoint = socket.LocalEndPoint as IPEndPoint; - return endPoint.Address.ToString(); - } - } - /// /// Generate valid parameters for percent values /// diff --git a/YeelightAPI/DeviceGroup.IDeviceController.cs b/YeelightAPI/DeviceGroup.IDeviceController.cs index ff1b6f3..a00749c 100644 --- a/YeelightAPI/DeviceGroup.IDeviceController.cs +++ b/YeelightAPI/DeviceGroup.IDeviceController.cs @@ -1,4 +1,9 @@ -using System.Threading.Tasks; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using YeelightAPI.Core; using YeelightAPI.Models; using YeelightAPI.Models.Adjust; using YeelightAPI.Models.ColorFlow; @@ -242,17 +247,73 @@ public async Task StartColorFlow(ColorFlow flow) } /// - /// starts the music mode for all devices + /// starts the music mode for all devices, with automatic port chosing starting at /// /// - /// + /// /// - public async Task StartMusicMode(string hostName, int port) + public async Task StartMusicMode(string hostName, int? startingPort) { - return await Process((Device device) => + startingPort = startingPort ?? Constants.DefaultMusicModeStartingPort; + bool result = true; + //this one can't be parallelized because of ports + foreach (var device in this) { - return device.StartMusicMode(hostName, port); - }); + int port = NetworkHelper.GetNextAvailablePort(startingPort.Value); + result &= await device.StartMusicMode(hostName, port); + } + + return result; + } + + /// + /// starts the music mode for all device, with specified ports + /// + /// + /// + /// + /// + public async Task StartMusicMode(string hostName, IEnumerable ports) + { + if(ports == null || ports.Count() != this.Count) + { + throw new InvalidDataException("Specified ports does not match with DeviceGroup length"); + } + + bool result = true; + int idx = 0; + //this one can't be parallelized because of ports + foreach (var device in this) + { + int port = ports.ElementAt(idx); + result &= await device.StartMusicMode(hostName, port); + } + + return result; + } + + /// + /// starts the music mode for all device, with possibility to specify a Func that will be called for reach device to determine its port + /// + /// + /// + /// + /// + public async Task StartMusicMode(string hostName, Func portChooser) + { + if (portChooser == null) + { + throw new InvalidDataException("portChooser parameter cannot be null"); + } + + bool result = true; + //this one can't be parallelized because of ports + foreach (var device in this) + { + result &= await device.StartMusicMode(hostName, portChooser); + } + + return result; } /// diff --git a/YeelightAPI/DeviceGroup.cs b/YeelightAPI/DeviceGroup.cs index a63e40b..26a2996 100644 --- a/YeelightAPI/DeviceGroup.cs +++ b/YeelightAPI/DeviceGroup.cs @@ -104,6 +104,10 @@ protected async Task Process(Func> f) return result; } + /// + /// To string + /// + /// public override string ToString() { return $"{this.Name} ({this.Count} devices)"; diff --git a/YeelightAPI/DeviceLocator.cs b/YeelightAPI/DeviceLocator.cs index ae00982..010b4f2 100644 --- a/YeelightAPI/DeviceLocator.cs +++ b/YeelightAPI/DeviceLocator.cs @@ -179,7 +179,7 @@ public static async Task> DiscoverAsync( CancellationToken cancellationToken) => (await DeviceLocator.SearchNetworkForDevicesAsync(networkInterface, deviceFoundReporter, cancellationToken)).Devices; -#if NETSTANDARD2_1 || NET5_0 +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER /// /// Enumerate devices asynchronously. /// diff --git a/YeelightAPI/Interfaces/IDeviceController.cs b/YeelightAPI/Interfaces/IDeviceController.cs index ee40afd..dce1e28 100644 --- a/YeelightAPI/Interfaces/IDeviceController.cs +++ b/YeelightAPI/Interfaces/IDeviceController.cs @@ -1,4 +1,8 @@ -using System.Threading.Tasks; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using YeelightAPI.Core; using YeelightAPI.Models; using YeelightAPI.Models.Adjust; using YeelightAPI.Models.ColorFlow; @@ -143,12 +147,30 @@ public interface IDeviceController Task StartColorFlow(ColorFlow flow); /// - /// Start the music mode + /// starts the music mode for all devices, with automatic port chosing /// /// - /// + /// /// - Task StartMusicMode(string hostName, int port); + Task StartMusicMode(string hostName, int? startingPort); + + /// + /// starts the music mode for all device, with specified ports + /// + /// + /// + /// + /// + Task StartMusicMode(string hostName, IEnumerable ports); + + /// + /// starts the music mode for all device, with possibility to manually specify port for each device + /// + /// + /// + /// + /// + Task StartMusicMode(string hostName, Func portChooser); /// /// Stop the current color flow diff --git a/YeelightAPI/YeelightAPI.csproj b/YeelightAPI/YeelightAPI.csproj index 620530d..f1952fa 100644 --- a/YeelightAPI/YeelightAPI.csproj +++ b/YeelightAPI/YeelightAPI.csproj @@ -1,22 +1,23 @@  - netstandard2.0; netstandard2.1; net45; net46; net47; net48; net5.0 + netstandard2.0; netstandard2.1; net45; net451; net452; net46; net461; net462; net47; net471; net472; net48; netcoreapp2.0; netcoreapp2.1; netcoreapp2.2; netcoreapp3.0; netcoreapp3.1; net5.0; net6.0 Romain ODDONE Romain ODDONE - 1.10.2 + 1.11.0 true true https://github.com/roddone/YeelightAPI https://github.com/roddone/YeelightAPI Github xiaomi yeelight api bulb device iot .net c# - * [Device] fix an error that caused the device to be considered in music mode even if the SetMusicMode failed + * [Device and DeviceGroup] Add more ways to handle Music Mode on Device and DeviceGroup + * Add more official build targets Xiaomi yeelight smart-devices manager - Copyright © Romain ODDONE 2021 + Copyright © Romain ODDONE 2022 true - 1.10.2.0 - 1.10.2.0 + 1.11.0.0 + 1.11.0.0 LICENSE icon.png @@ -34,7 +35,7 @@ - + diff --git a/YeelightAPI/packages.config b/YeelightAPI/packages.config index e4ac9c6..ce3dc38 100644 --- a/YeelightAPI/packages.config +++ b/YeelightAPI/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file