Skip to content

Commit

Permalink
Merge pull request #45 from roddone/feature/music-mode
Browse files Browse the repository at this point in the history
Feature/music mode
  • Loading branch information
roddone authored Aug 11, 2020
2 parents 6a76fb5 + 29276c4 commit bf3bca9
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 83 deletions.
38 changes: 34 additions & 4 deletions YeelightAPI/Device.IDeviceController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ public void Disconnect()
{
_tcpClient.Close();
_tcpClient = null;
try
{
_watchCancellationTokenSource.Cancel();
}
catch (ObjectDisposedException) { }
}
}

Expand Down Expand Up @@ -332,17 +337,34 @@ public async Task<bool> StartColorFlow(ColorFlow flow)
/// <summary>
/// Starts the music mode
/// </summary>
/// <param name="hostName"></param>
/// <param name="hostname"></param>
/// <param name="port"></param>
/// <returns></returns>
public async Task<bool> StartMusicMode(string hostName, int port)
public async Task<bool> StartMusicMode(string hostname = null, int port = 12345)
{
List<object> parameters = new List<object>() { (int)MusicAction.On, hostName, port };
this.IsMusicModeEnabled = true;
//init new TCP socket
if (string.IsNullOrWhiteSpace(hostname))
{
hostname = GetLocalIpAddress();
}

var listener = new TcpListener(System.Net.IPAddress.Parse(hostname), port);
listener.Start();

List<object> parameters = new List<object>() { (int)MusicAction.On, hostname, port };

CommandResult<List<string>> result = await ExecuteCommandWithResponse<List<string>>(
method: METHODS.SetMusicMode,
parameters: parameters);

if (result.IsOk())
{
this.Disconnect();
var musicTcpClient = await listener.AcceptTcpClientAsync();
_tcpClient = musicTcpClient;
}

return result.IsOk();
}

Expand Down Expand Up @@ -370,7 +392,15 @@ public async Task<bool> StopMusicMode()
method: METHODS.SetMusicMode,
parameters: parameters);

return result.IsOk();
if (result.IsOk())
{
//disables the music mode
this.IsMusicModeEnabled = false;
await DisableMusicModeAsync();
return true;
}

return false;
}

/// <summary>
Expand Down
215 changes: 137 additions & 78 deletions YeelightAPI/Device.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
Expand Down Expand Up @@ -37,6 +38,11 @@ public partial class Device : IDisposable
/// </summary>
private TcpClient _tcpClient;

/// <summary>
/// Cancellation token source for the Watch task
/// </summary>
private CancellationTokenSource _watchCancellationTokenSource;

#endregion PRIVATE ATTRIBUTES

#region EVENTS
Expand Down Expand Up @@ -90,6 +96,11 @@ public bool IsConnected
}
}

/// <summary>
/// Indicate wether the music mode is enabled
/// </summary>
public bool IsMusicModeEnabled { get; private set; }

/// <summary>
/// The model.
/// </summary>
Expand Down Expand Up @@ -216,21 +227,6 @@ public object this[string propertyName]

#region PUBLIC METHODS

#region IDisposable

/// <summary>
/// Dispose the device
/// </summary>
public void Dispose()
{
lock (_syncLock)
{
Disconnect();
}
}

#endregion IDisposable

/// <summary>
/// Execute a command
/// </summary>
Expand All @@ -251,6 +247,15 @@ public void ExecuteCommand(METHODS method, List<object> parameters = null)
/// <returns></returns>
public async Task<CommandResult<T>> ExecuteCommandWithResponse<T>(METHODS method, List<object> parameters = null)
{
if (IsMusicModeEnabled)
{
//music mode enabled, there will be no response, we should assume everything works
int uniqueId = GetUniqueIdForCommand();
ExecuteCommand(method, uniqueId, parameters);
return new CommandResult<T>() { Id = uniqueId, Error = null, IsMusicResponse = true };
}

//default behavior : send command and wait for response
return await ExecuteCommandWithResponse<T>(method, GetUniqueIdForCommand(), parameters);
}

Expand All @@ -261,7 +266,7 @@ public async Task<CommandResult<T>> ExecuteCommandWithResponse<T>(METHODS method
/// <returns></returns>
public override string ToString()
{
return $"{this.Model.ToString()} ({this.Hostname}:{this.Port})";
return $"{Model.ToString()} ({Hostname}:{Port})";
}

#endregion PUBLIC METHODS
Expand Down Expand Up @@ -316,10 +321,27 @@ internal async Task<CommandResult<T>> ExecuteCommandWithResponse<T>(METHODS meth
return null;
}

internal async Task DisableMusicModeAsync()
{
_ = await Connect();
IsMusicModeEnabled = false;

}

#endregion INTERNAL METHODS

#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();
}
}

/// <summary>
/// Generate valid parameters for percent values
/// </summary>
Expand Down Expand Up @@ -430,99 +452,102 @@ private async Task<CommandResult<T>> UnsafeExecuteCommandWithResponse<T>(METHODS
/// <returns></returns>
private async Task Watch()
{
await Task.Factory.StartNew(async () =>
using (_watchCancellationTokenSource = new CancellationTokenSource())
{
//while device is connected
while (_tcpClient != null)
await Task.Run(async () =>
{
lock (_syncLock)
//while device is connected
while (_tcpClient != null && _watchCancellationTokenSource.IsCancellationRequested == false)
{
if (_tcpClient != null)
lock (_syncLock)
{
//automatic re-connection
if (!_tcpClient.IsConnected())
if (_tcpClient != null)
{
_tcpClient.ConnectAsync(Hostname, Port).Wait();
}
//automatic re-connection
if (!_tcpClient.IsConnected())
{
_tcpClient.ConnectAsync(Hostname, Port).Wait();
}

if (_tcpClient.IsConnected())
{
//there is data avaiblable in the pipe
if (_tcpClient.Client.Available > 0)
if (_tcpClient.IsConnected())
{
byte[] bytes = new byte[_tcpClient.Client.Available];
//there is data avaiblable in the pipe
if (_tcpClient.Client.Available > 0)
{
byte[] bytes = new byte[_tcpClient.Client.Available];

//read datas
_tcpClient.Client.Receive(bytes);
//read datas
_tcpClient.Client.Receive(bytes);

try
{
string datas = Encoding.UTF8.GetString(bytes);
if (!string.IsNullOrEmpty(datas))
try
{
//get every messages in the pipe
foreach (string entry in datas.Split(new string[] { Constants.LineSeparator },
StringSplitOptions.RemoveEmptyEntries))
string datas = Encoding.UTF8.GetString(bytes);
if (!string.IsNullOrEmpty(datas))
{
CommandResult commandResult =
JsonConvert.DeserializeObject<CommandResult>(entry, Constants.DeviceSerializerSettings);
if (commandResult != null && commandResult.Id != 0)
//get every messages in the pipe
foreach (string entry in datas.Split(new string[] { Constants.LineSeparator },
StringSplitOptions.RemoveEmptyEntries))
{
ICommandResultHandler commandResultHandler;
lock (_currentCommandResults)
CommandResult commandResult =
JsonConvert.DeserializeObject<CommandResult>(entry, Constants.DeviceSerializerSettings);
if (commandResult != null && commandResult.Id != 0)
{
if (!_currentCommandResults.TryGetValue(commandResult.Id, out commandResultHandler))
continue; // ignore if the result can't be found
}
ICommandResultHandler commandResultHandler;
lock (_currentCommandResults)
{
if (!_currentCommandResults.TryGetValue(commandResult.Id, out commandResultHandler))
continue; // ignore if the result can't be found
}

if (commandResult.Error == null)
{
commandResult = (CommandResult)JsonConvert.DeserializeObject(entry, commandResultHandler.ResultType, Constants.DeviceSerializerSettings);
commandResultHandler.SetResult(commandResult);
if (commandResult.Error == null)
{
commandResult = (CommandResult)JsonConvert.DeserializeObject(entry, commandResultHandler.ResultType, Constants.DeviceSerializerSettings);
commandResultHandler.SetResult(commandResult);
}
else
{
commandResultHandler.SetError(commandResult.Error);
}
}
else
{
commandResultHandler.SetError(commandResult.Error);
}
}
else
{
NotificationResult notificationResult =
JsonConvert.DeserializeObject<NotificationResult>(entry,
Constants.DeviceSerializerSettings);
NotificationResult notificationResult =
JsonConvert.DeserializeObject<NotificationResult>(entry,
Constants.DeviceSerializerSettings);

if (notificationResult != null && notificationResult.Method != null)
{
if (notificationResult.Params != null)
if (notificationResult != null && notificationResult.Method != null)
{
//save properties
foreach (KeyValuePair<PROPERTIES, object> property in
notificationResult.Params)
if (notificationResult.Params != null)
{
this[property.Key] = property.Value;
//save properties
foreach (KeyValuePair<PROPERTIES, object> property in
notificationResult.Params)
{
this[property.Key] = property.Value;
}
}
}

//notification result
OnNotificationReceived?.Invoke(this,
new NotificationReceivedEventArgs(notificationResult));
//notification result
OnNotificationReceived?.Invoke(this,
new NotificationReceivedEventArgs(notificationResult));
}
}
}
}
}
}
catch (Exception ex)
{
OnError?.Invoke(this, new UnhandledExceptionEventArgs(ex, false));
catch (Exception ex)
{
OnError?.Invoke(this, new UnhandledExceptionEventArgs(ex, false));
}
}
}
}
}
}

await Task.Delay(100);
}
}, TaskCreationOptions.LongRunning);
await Task.Delay(100);
}
}, _watchCancellationTokenSource.Token);
}
}

/// <summary>
Expand All @@ -535,5 +560,39 @@ private int GetUniqueIdForCommand()
}

#endregion PRIVATE METHODS

#region IDisposable

private void ReleaseUnmanagedResources()
{
// TODO release unmanaged resources here
}

private void Dispose(bool disposing)
{
ReleaseUnmanagedResources();
if (disposing)
{
lock (_syncLock)
{
Disconnect();
}
}
}

/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

/// <inheritdoc />
~Device()
{
Dispose(false);
}

#endregion IDisposable
}
}
5 changes: 5 additions & 0 deletions YeelightAPI/DeviceGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ protected async Task<bool> Process(Func<Device, Task<bool>> f)
return result;
}

public override string ToString()
{
return $"{this.Name} ({this.Count} devices)";
}

#endregion Protected Methods
}
}
Loading

0 comments on commit bf3bca9

Please sign in to comment.