Giter Site home page Giter Site logo

roddone / yeelightapi Goto Github PK

View Code? Open in Web Editor NEW
155.0 13.0 27.0 414 KB

C# API (.Net) to control Xiaomi Yeelight devices

License: Apache License 2.0

C# 100.00%
xiaomi yeelight csharp bulb yeelight-devices dotnet smart-bulbs nuget wifi-device iot windows library

yeelightapi's Introduction

Build Status NuGet Package NuGet Downloads Donate

YeelightAPI

C# API (.Net) to control Xiaomi Yeelight Color Bulbs

Contribution

If you find this package useful, please make a gift on Paypal : https://www.paypal.me/roddone

Prerequisites

  • The console project uses C# 7.1 "Async Main Method" Feature, make sure your visual studio version is up to date !
  • Enable the "developer mode" on your devices, otherwhise they will neither be discovered nor usable by this API

Installation

To install the latest release from NuGet package manager:

Install-Package YeelightAPI

Usage

Single Device

The YeelightAPI.Device allows you to create a device. Just instanciate a new Device with an ip adress or a hostname: Device device = new Device("hos.tna.meo.rIP"); and initiate connection : device.Connect();. Then you can use the device object to control the device :

  • Power on / off : device.SetPower(true);
  • Toggle State : device.Toggle();
  • Change brightness level : device.SetBrightness(100);
  • Change color : device.SetRGBColor(80, 244, 255);
  • ...

Some methods use an optional parameter named "smooth", it refers to the duration in milliseconds of the effect you want to apply. For a progressive brightness change, use device.SetBrightness(100, 3000);.

If you need a method that is not implemented, you can use the folowing methods :

  • ExecuteCommandWithResponse(METHODS method, int id = 0, List<object> parameters = null) (with response)
  • ExecuteCommand(METHODS method, int id = 0, List<object> parameters = null) (without response).

These methods are generic and use the METHODS enumeration and a list of parameters, which allows you to call any known method with any parameter. All the parameters are defined in the doc "Yeelight WiFi Light Inter-Operation Specification", section 4.1 : COMMAND Message.

Multiple-devices

If you need to control multiple devices at a time, you can use the YeelightAPI.DeviceGroup class. This class simply ihnerits from native .net List<Device> and implements the IDeviceController interface, allowing you to control multiple devices the exact same way you control a single device.

DeviceGroup group = new DeviceGroup();
group.Add(device1);
group.Add(device2);

group.Connect();
group.Toggle();
...

Color Flow

Old School

You can create a Color Flow to "program" your device with different state changes. Changes can be : RGB color, color temperature and brightness. Just create a new ColorFlow(), add some new ColorFlowExpression() to it, and starts the color flow with your ColorFlow object.

ColorFlow flow = new ColorFlow(0, ColorFlowEndAction.Restore);
flow.Add(new ColorFlowRGBExpression(255, 0, 0, 1, 500)); // color : red / brightness : 1% / duration : 500
flow.Add(new ColorFlowRGBExpression(0, 255, 0, 100, 500)); // color : green / brightness : 100% / duration : 500
flow.Add(new ColorFlowRGBExpression(0, 0, 255, 50, 500)); // color : blue / brightness : 50% / duration : 500
flow.Add(new ColorFlowSleepExpression(2000)); // sleeps for 2 seconds
flow.Add(new ColorFlowTemperatureExpression(2700, 100, 500)); // color temperature : 2700k / brightness : 100 / duration : 500
flow.Add(new ColorFlowTemperatureExpression(5000, 1, 500)); // color temperature : 5000k / brightness : 100 / duration : 500

device.StartColorFlow(flow); // start

/* Do Some amazing stuff ... */

device.StopColorFlow(); // stop the color flow

The ColorFlow constructor has 2 parameters : the first one defines the number of repetitions (or 0 for infinite), the second one defines what to do when the flow is stopped. you can choose to restore to the previous state, keep the last state or turn off the device.

Fluent

Another way to create color flow is to use the device.Flow() method. This method returns a FluentFLow object you can use to create a flow in a "Fluent-syntax" way. example :

FluentFlow flow = await backgroundDevice.BackgroundFlow()
  .RgbColor(255, 0, 0, 50, 1000)
  .Sleep(2000)
  .RgbColor(0, 255, 0, 50) //without timing
  .During(1000) // set the timing of the previous instruction
  .Sleep(2000)
  .RgbColor(0, 0, 255, 50, 1000)
  .Sleep(2000)
  .Temperature(2700, 100, 1000)
  .Sleep(2000)
  .Temperature(6500, 100, 1000)
  .Play(ColorFlowEndAction.Keep);

await flow.StopAfter(5000);

//use the same object to create a new flow
await flow.Reset()
  .RgbColor(0, 255, 0, 50, 1000)
  .Temperature(3000, 100, 1000)
  .Play(ColorFlowEndAction.Keep);

Find devices

If you want to find all connected devices, you can use the static asynchronous API of the YeelightAPI.DeviceLocator:

private	async Task GetDevicesAsync()
{
  // Await the asynchronous call to the static API
  IEnumerable<Device> discoveredDevices = await DeviceLocator.DiscoverAsync();
}

Device Found

If you don't want to wait until all devices are discovered, you can make use of the IProgress<T>, to receive intermediate results.
Create instance of Progress<Device> and pass it to the appropriate DiscoverAsync overload. The callback, taking a Device as parameter and is registered with the constructor will always execute on the caller's thread. Therefore the caller has not to worry about Dispatcher invokes.
Each time DeviceLocator.DiscoverAsync(IProgress<Device>) finds a device, the IProgress<T>.Report method is invoked with the discovered devcice, which will trigger a call to the registered callback. Example :

// Define the callback for the progress reporter
private void OnDeviceFound(Device device) 
{
  // Do Something with the discovered device   
}
	
private	async Task GetDevicesAsync()
{
  // Initialize the instance of Progress<T> with a callback to handle a discovered device
  var progressReporter = new Progress<Device>(OnDeviceFound);
  
  // Await the asynchronous call to the static API
  await DeviceLocator.DiscoverAsync(progressReporter);
  
  // Alternatively: although each device is handled as soon as it is discovered by the callback registered with the progress reporter, 
  // you still can await the result collection
  IEnumerable<Device> discoveredDevices = await DeviceLocator.DiscoverAsync(progressReporter);
}

Parameters

Some parameters are available to help avoiding some issues during discovery

  • MaxRetryCount allows you to define a retry count. Use with caution, because it can slow down the discovery! Defaults to 1.
  • UseAllAvailableMulticastAddresses allows you to use all the available multicast addresses instead of just the default one : 239.255.255.250. Use with caution, because it can slow down the discovery! Defaults to false.
  • DefaultMulticastIPAddress allows you to change the default multicast address used for the discovery. Defaults to 239.255.255.250

Async / Await

Almost every method is awaitable returning a Task or Task<T> to make them execute asynchronously. Simply await them non-blocking using await: Example :

// with single device
await device.Connect();
await device.Toggle();

// with groups
await group.Connect();
await group.Toggle();
...

Events

Notifications

When you call a method that changes the state of the device, it sends a notification to inform that its state really change. You can receive these notification using the "OnNotificationReceived" event. Example :

device.OnNotificationReceived += (object sender, NotificationReceivedEventArgs arg) =>
{
  Console.WriteLine("Notification received !! value : " + JsonConvert.SerializeObject(arg.Result));
};

Errors

When an unknown error occurs, a "OnError" event is fired. Example :

device.OnError += (object sender, UnhandledExceptionEventArgs e) =>
{
  Console.WriteLine($"An error occurred : {e.ExceptionObject}");
};

VNext

  • correct bugs if needed

Nothing else planned, if you have any ideas feel free to create an issue or a pull request.

Licence

Apache Licence

Source

This code is an implementation of the "Yeelight WiFi Light Inter-Operation Specification" as defined on January 1st, 2018

yeelightapi's People

Contributors

bioniccode avatar dainius14 avatar dsherret avatar hangy avatar mjwsteenbergen avatar nathanpovo avatar p-monteiro avatar roddone avatar thoemmi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

yeelightapi's Issues

[Feature-request][Question]

Hello, this is more of a feature request/inquiry, versus a bug.

I am curious - for the setRgbColor function, is this sent over HTTP?

I would like to use this API with an application of mine that sends streaming color data to lights in realtime. Ideally, this would be done over UDP for speed of transmission/repetition.

As such, I'm wondering if Yeelight has a "streaming" protocol, and if so, if this API exposes it in any way. If there is and it's not exposed - would it be possible to support this functionality? Looking to send color data anywhere from 30-60 times per second.

DeviceLocator.UseAllAvailableMulticastAddresses not returning all devices in v1.10.2.

Describe the bug
Hi when using DeviceLocator.UseAllAvailableMulticastAddresses in the latest v1.10.2, i get only 2 out of 4 total devices.
When i was using v1.8.0 i was getting 4/4 devices using this option

To Reproduce
DeviceLocator.UseAllAvailableMulticastAddresses = true;
var devices = await DeviceLocator.DiscoverAsync();

Expected behavior
DeviceLocator.DiscoverAsync(); to return 4/4 devices on the network but only returns 2/4

Environment (please complete the following information):

  • OS: [Windows 10]
  • .Net version [.NET 6]
  • WPF Project

Suggestion to change the meaning of repetition parameter for color flows

While fixing #21, I discovered a weird behavior in the Yeelight specification. If you execute this code

await device.Flow()
    .RgbColor(255,   0, 0, 50, 2000)
    .RgbColor(  0, 255, 0, 50, 2000)
    .Play(ColorFlowEndAction.TurnOff, 4);

, I would expect the light to turn from red to green four times. However, it turns to green only two times. I consulted the specification, and in fact it states for the start_cf method:

"count" is the total number of visible state changing before color flow stopped. 0 means infinite loop on the state changing.

Because the flow already consists of two state changes, the loops stops after 2 repetitions (2 state changes * 2 repetitions = count of 4).

That's not what I expected, and I guess other users may be surprised too. I've been always a fan of the principle of least surprise. Therefore I suggest to change the semantic of the repetition parameter. I propose to change the implementation of StartColorFlow from

List<object> parameters = new List<object>()
{
    flow.RepetitionCount,
    (int)flow.EndAction,
    flow.GetColorFlowExpression()
};

to

List<object> parameters = new List<object>()
{
    flow.RepetitionCount * flow.Count, // <-- multiply repetitions by number of flow actions
    (int)flow.EndAction,
    flow.GetColorFlowExpression()
};

How do you think about it?

No devices found via SSDP

Hi,

I have some Yeelight bulbs that works fine with native phone app and Google Home but i cannot find devices by YeelightApiConsoleTest app in discovery mode. It say that "No devices found via SSDP".
I also tried to use static ip addres taken from the app and use default port and get the error:

An error has occurred : No connection could be made because the target machine actively refused it 192.168.100.16:55443

Only works once

I tried to use the API however I can only use one command one time in one run. Can anyone tell what to do with this problem?

[BUG] Performing actios without smooth transitio doels not work

Describe the bug
A clear and concise description of what the bug is.

To Reproduce
Steps to reproduce the behavior:

  1. Connect to the device.
  2. Perform an action without passing the smooth parameter (null).

Expected behavior
Action immediately happens, e.g. device turns on, temperature is set.

Actual behavior
Action does not happen. Exception is thrown -5001 - invalid params

Environment (please complete the following information):

  • OS: Windows 10 1903
  • .Net Core 3
  • Yeeight White 2 FW: 2.0.6_0035
  • Yeeight RGB 2 FW: 2.0.6_0051

I've spent so much time figuring out why it did not work oh my lord...

[BUG] LED Ceiling Light brightness always returns 1 in moonlight mode

Describe the bug
When in Moonlight mode, my Ceiling LED always returns brightness = 1
but the set works well from 1 to 100

To Reproduce
I have this code that proves the bug:

Console.WriteLine("curr value: " + await device.GetProp(PROPERTIES.bright)); // always 1
brightnessSlider.ValueChanged += async (sender, eventArgs) => {
     var value = Convert.ToInt32(Math.Round(eventArgs.NewValue));
     var x = await device.SetBrightness(value);
     Console.WriteLine("OK: " + x);
     Console.WriteLine("slider value: " + eventArgs.NewValue);
     Console.WriteLine("put value: " + value);
     Console.WriteLine("curr value: " + await device.GetProp(PROPERTIES.bright)); // always 1
}

Expected behavior
I'd expect the get brightness to change.
device.GetProp(PROPERTIES.bright) should return a value from 1 to 100

Stacktrace

{"FirmwareVersion":"22","Properties":{"power":"on","bright":"1","color_mode":"2","ct":"3200","rgb":"","hue":"","sat":"","name":"LED TV","flowing":"0","delayoff":"0","flow_params":"
","music_on":"","bg_power":"","bg_flowing":"","bg_flow_params":"","bg_ct":"","bg_lmode":"","bg_bright":"","bg_rgb":"","bg_hue":"","bg_sat":"","nl_br":"11","active_mode":"1"},"Suppo
rtedOperations":[1,8,6,7,5,11,12,13,14,9,10,2,17,15,30,31],"Hostname":"192.168.1.223","Id":"0x000000000805baaa","IsConnected":true,"Model":0,"Port":55443,"Name":"LED TV"}

When using the slider:

OK: True
slider value: 26
put value: 26
curr value: 1

OK: True
slider value: 61
put value: 61
curr value: 1

Environment (please complete the following information):

  • OS: Windows
  • .Net version .Net Core 3.1.102

In case of multiple NICs (not connected), DeviceLocator.Discover() Throws Exception.

Hey,
I have multiple NICs installed on my machine (some are virtual for Docker, some are just Ethernet, Wi-Fi, some are for HyperV).
If I'm connecting through Wi-Fi but also other adapters are enabled (enabled but not connected), I get this Exception when calling Discover method:

System.Net.Sockets.SocketException: "The requested address is not valid in its context"

If I disable all adapters apart from the one currently connected, then everything work as expected.

From what I understood, Discover method is sending the discover message using all NICs avaialble.
Maybe you could have an override, where one select the preferred NIC somehow, or maybe some sort of try catch logic to mitigate this kind of issue.

The same device is discovered multiple times

I noticed that you're sending the discovery message three times. Because the devices property isn't locked, I sometimes get two or even three Device instances, although I only have on physical device.
Is there a reason why you sending the message three times? And if this is really necessary, consider using lock for exclusive access to devices, e.g.

lock (devices)
{
    //add only if no device already matching
    if (!devices.Any(d => d.Hostname == device.Hostname))
    {
        devices.Add(device);
    }
}

(devices can be simple List<Device>, by the way. ConcurrentBag doesn't help you, because between the calls to Any and Add, another thread might add the same device)

[SOLVED] Connect issue

Hi,

I'm new with YeelightAPI.
I'm trying this simple code :

try
{
	List<Device> lo_devices = (await DeviceLocator.DiscoverAsync()).ToList();

	foreach (Device lo_device in lo_devices)
	{
		await lo_device.Connect();
		await lo_device.SetRGBColor(0, 255, 0, 1000);
	}

}
catch (Exception ex)
{
	MessageBox.Show(ex.Message);
}

The bulb is correctly discovered, but on the Connect() I get this error :

Une tentative de connexion a échoué car le parti connecté n’a pas répondu convenablement au-delà d’une certaine durée ou une connexion établie a échoué car l’hôte de connexion n’a pas répondu 192.168.1.88:55443

For information, I'm able to pilot the bulb with the Android app.

TIA

[BUG] DeviceGroup.StartMusicMode() won't work for multiple devices

Describe the bug
When using the method DeviceGroup.StartMusicMode(), the parameters are a single hostname and port. The problem is that starting multiple TcpListener using the same port will throw an exception:
System.Net.Sockets.SocketException: Only one usage of each socket address (protocol/network address/port) is normally permitted

To Reproduce
Steps to reproduce the behavior:

  1. Find or create multiple new Device() (at least 2)
  2. Group them with new DeviceGroup(IEnumerable)
  3. Call DeviceGroup.Connect() and then call DeviceGroup.StartMusicMode("x.x.x.x", 6969);
  4. See error

Expected behavior
The lib should start the music mode in every device added.

Stacktrace
System.Net.Sockets.SocketException: Normalmente é permitida apenas uma utilização de cada endereço de soquete (protocolo/endereço de rede/porta)
em System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress)
em System.Net.Sockets.Socket.Bind(EndPoint localEP)
em System.Net.Sockets.TcpListener.Start(Int32 backlog)
em System.Net.Sockets.TcpListener.Start()
em YeelightAPI.Device.d__97.MoveNext()

Environment (please complete the following information):

  • OS: Windows 10 x64
  • .NET Framework 4.8

Additional context
You can bypass this error by looping through each device and calling Device.StartMusicMode(null, uniquePort);

Set black Color

Hi,
I would to set the "black" color instead to turn off the light, but it seems that

device.SetRGBColor(0, 0, 0);

doesn't take any effect. If I try to set rgb(1,1,1) it works, but it really far from total blackness.

I've read Yeelight API documentation and (0,0,0) is a valid value

Unable to detect Mi LED Smart Bulb[BUG]

downloaded this app and wanted to test out Mi LED Smart Bulb (White and Color).

Doesn't seen to be working. Isn't Mi bulbs and Yeelights using same protocol? I can't find related codes to do with Mi smart lights but keep getting Yeelight as the search result.

[BUG]

Description
Async code in class DeviceLocator uses the blocking synchronous Thread.Sleep() in an asynchronous context. For efficiency it is recommended to prefer the awaitable Task library over the Thread API.

Check library for other occurrences.

Expected behavior
DeviceLocator line 158 should be changed to await Task.Delay(TimeSpan.FromMilliseconds(10))

It is not working

Hello, I have a problem with this. My yeelight is not running on port 55443. Please how can I get working port?

Socket error

Im getting System.Net.Sockets.SocketException: 'An existing connection was forcibly closed by the remote host'

Event that notify a light is discovered

Hi roddone,
this library is practically perfect, but I'm missing an event that notifies me every time a light is discovered. Actually discovery (await DeviceLocator.Discover()) is synchronous: I have to wait the full process is ended before showing the new lights

An event-driven approach is effective when developing GUI, it helps to reduce the time between button press and when new light appears on the screen.

[BUG]

Description
Library produces unexpected side effects when listening to the DeviceFound event and the event handler manipulates the UI. See Stack overflow post for details.

Since ALL exceptions are swallowed by an empty catch block (at least in the DeviceLocator class), the client code shows unexpected behavior in case of an exception, as it continues to execute in an unpredictable state. In case of the event handler is accessing the UI, a cross thread exception is expected to be thrown by the .Net framework.
The library should either let the exception reach the client code or/and marshal the event handler execution to the UI thread, by raising the even on the dispatcher: Application.Current.Dispatcher.InvokeAsync(() => OnDeviceFound?.Invoke(null, new DeviceFoundEventArgs(device)));. DeviceLocator line 154.

A library should never swallow exceptions as this can lead to unexpected side effects. The client must be given the choice to decide and handle exceptions individually.

Check library for more occurrences.

Expected behavior
The library should either let the exception reach the client code or/and marshal the event handler execution to the UI thread, by raising the even on the dispatcher: Application.Current.Dispatcher.InvokeAsync(() => OnDeviceFound?.Invoke(null, new DeviceFoundEventArgs(device)));

Environment:

  • OS: Windows
  • WPF

Q: Support for music mode?

I have hit the command quota with my Yeelight when trying to use it as an ambient light. I was setting it to the average screen color in a 100 ms interval. According to the documentation only 60 commands per second are allowed unless the device is in "music mode".

I see that I can activate music mode via Device.StartMusicMode(string hostName, int port). I also understand that this means I have to create a secondary TCP server which my Yeelight can connect to and then feed it the commands over this channel.

However, it seems there is no API support for this? Meaning I will have to generate the JSON for the commands myself and can't use your library for music mode connections. Is this correct?

color flow issue

Yeelight Firmware: 1.4.2_0061

Hi there,

I´m not able to get the ColorFlow work, here`s my code.

Device device = new Device("192.168.178.xxx");
ColorFlow colorFlow = new ColorFlow(0, ColorFlowEndAction.TurnOff);

colorFlow.Add(new ColorFlowRGBExpression(255,255,255,100, 1000));
colorFlow.Add(new ColorFlowRGBExpression(0, 0, 0, 0, 1000));

await device.Connect();
await device.SetPower(true);
await device.StartColorFlow(colorFlow);

await device.SetRGBColor(255, 102, 255); works just fine.

[BUG] DeviceLocator throwing InvalidOperationException

Hey,

I think I have discovered a bug in your library. The DeviceLocator.DiscoverAsync method throws an InvalidOperationException with the message: "Sequence contains no matching element"

I'm using this simple code to get all the devices:
var devices = await DeviceLocator.DiscoverAsync();

I'm using your library in an Xamarin application. There I get this exception even when I try with your sample code you used in the console sample app (DeviceLocator + Progress reporter)

Any ideas? And yes developer mode is activated on all devices ^^

Device.CronGet does not work

Nice to see that you have added support for cron operations in 21089c6. Unfortunately, your code does not work:

In Device.Watch you're deserializing to a CommandResult, which is derived from CommandResult<List<string>>. But because the response is not an array of strings but an JSON object (or at least a hash map), Device.Watch cannot deserialize it, and commandResult.Result is null.

[BUG]

Hello, I have an error on connect my device : No connection could be established because the target computer expressly refused it

Can you help me ? Thanks

[QUESTION] Should I create new tcp connection on every http request or persist an exist one ?

Hi,
I integrate your awesome Yeelight API with my .NET 6 Web API. I create new Yeelight device and make a connection only once (yeelight device object is stored as property of YeelightDeviceService class (singleton). I've noticed that if I don't use the device for a long time, I get a "Broken pipe" error when I try to run a command.
Should I keep the connection or on every http request establish a new connection then invoke the command and terminate the tcp connection?
Could you advise me on what I should do.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.