Controlling a light
A YeelightDevice is a light that the client found (see Finding lights). Every command is an
async method that returns when the light has answered, and throws a YeelightException when it did not
succeed. This page lists the commands, what they accept, and what the state of a light means.
The main light and the background light
YeelightDevice is the main light of a device. It derives from YeelightLight, which has the state and the
commands of one light. A device that has a second light (some ceiling lights have a background light) offers it as
Background, a YeelightBackgroundLight with the same
commands and the same properties:
await light.SetBrightnessAsync(80); // the main light
if (light.SupportsBackgroundLight)
{
await light.Background.SetBrightnessAsync(20); // the background light
}
Background always exists as an object, but only a device that has a background light lists its methods: look at
SupportsBackgroundLight. The two lights share the connection of the device, and so
its limit of 60 commands a minute. What concerns the device as a whole (its name, its sleep timer, its connection, music mode) is only on the
device.
The commands
Power
await light.TurnOnAsync();
await light.TurnOffAsync(TimeSpan.FromSeconds(2)); // fades out over two seconds
await light.SetPowerAsync(true, mode: PowerOnMode.ColorTemperature); // on, in that mode
await light.ToggleAsync(); // on when it was off, off when it was on
await light.ToggleAllAsync(); // the main light and the background light together
TurnOnAsync, TurnOffAsync and
SetPowerAsync take a transition. A light that is off is switched off by
software: it still has power, and still answers. ToggleAsync flips the state without your having
to know it. ToggleAllAsync does that for the main light and the background light at the same
time.
PowerOnMode says in which mode the light is switched on, and is only for switching on (asking for a mode when
switching off is an ArgumentException):
| Mode | The light is switched on |
|---|---|
Normal |
as it was before it was switched off (the default) |
ColorTemperature, Rgb, Hsv |
and put in that colour mode |
ColorFlow |
and put in colour flow mode |
NightLight |
and put in night light mode (ceiling lights only) |
The mode is only sent when it is not Normal, because older firmware versions only know the three parameters before it.
Brightness, colour temperature and colour
using System.Drawing;
await light.SetBrightnessAsync(60); // 1 to 100 percent
await light.SetColorTemperatureAsync(2700); // 1700 to 6500 kelvin
await light.SetRgbAsync(255, 140, 0); // red, green and blue, each 0 to 255
await light.SetHsvAsync(210, 80); // hue 0 to 359, saturation 0 to 100
// A System.Drawing.Color works too (its alpha is ignored), with a transition.
await light.SetRgbAsync(Color.CornflowerBlue, TimeSpan.FromSeconds(1));
SetBrightnessAsync, SetColorTemperatureAsync,
SetRgbAsync and SetHsvAsync all take a
transition. A light that cannot show colours (a mono light has brightness only) does not list the colour commands: see
What the light supports. The range of colour temperatures of a light depends on its model; the library only checks
the range of the protocol, 1700 to 6500 kelvin.
Adjusting without knowing the current value
await light.AdjustAsync(AdjustAction.Increase, AdjustProperty.Brightness); // one step up
await light.AdjustAsync(AdjustAction.Circle, AdjustProperty.Color); // the next colour
await light.AdjustBrightnessAsync(-20, TimeSpan.FromMilliseconds(500)); // 20 percent less
await light.AdjustColorTemperatureAsync(20); // 20 percent more
await light.AdjustColorAsync();
AdjustAsync is for controllers, such as a dial or a remote: it steps a property up or down without
knowing its value, and the size of a step is the light's own. Circle steps up, and starts again at the bottom when it reaches the top; it is
the only action that is valid for the colour. AdjustBrightnessAsync and
AdjustColorTemperatureAsync change the value by a percentage of what it is now,
from -100 to 100, over a duration (null is the shortest time that a light supports, 30 milliseconds). AdjustColorAsync has no
amount: the protocol has a percentage there that the light ignores, and the client sends 0.
Saving the state as the default
await light.SetDefaultAsync();
SetDefaultAsync saves the current state of the light in its memory, so that it shows that
state when it is switched on at the wall again after a loss of power.
The name
await light.SetNameAsync("desk");
SetNameAsync stores a name on the light (at most 64 bytes in UTF-8). The Yeelight app keeps
its own names in the cloud, so the two can differ. The specification suggests that a name with characters that are not ASCII is encoded with
Base64 first; the client sends the text as you give it and reports it as the light does, so decode it yourself if you encode it.
Name is what the light reports, and is empty when it has none.
Timers
await light.SetSleepTimerAsync(TimeSpan.FromMinutes(20)); // switch off in 20 minutes
TimeSpan? timer = await light.GetSleepTimerAsync(); // null when there is none
Console.WriteLine(light.SleepTimerRemaining); // what the light reports
await light.CancelSleepTimerAsync();
A device has one sleep timer, and a new one replaces it. SetSleepTimerAsync takes whole
minutes (a part of a minute is rounded up), at least one, and is only accepted by a light that is on. The specification gives the range of the
remaining time that a light reports as 1 to 60 minutes, and no maximum for the timer itself: a light may refuse a longer one.
GetSleepTimerAsync returns the length that the light reports for its timer, and
SleepTimerRemaining is the time that is left, as the light reports it (TimeSpan.Zero when
there is no timer).
CancelSleepTimerAsync stops it.
Reading properties
await light.RefreshAsync(); // all the properties
// Some of them, by their names in the protocol.
var values = await light.GetPropertiesAsync(["power", "bright"]);
Console.WriteLine($"{values["power"]}, {values["bright"]}%");
RefreshAsync reads all the properties that the protocol defines with one command, and takes
them over, so that the properties of the objects are what the light says now. GetPropertiesAsync
reads the ones that you name, returns them as text, and takes them over as well. A name that the light does not know has an empty value.
Colour flows, scenes and music mode
A colour flow is a program that a light runs by itself, and a scene sets a state directly, also on a light that is off: see Colour flows and scenes. In music mode a light takes commands without the limit of 60 a minute: see Music mode.
Transitions
A command that changes what the light shows takes a TimeSpan? as its transition. null, or TimeSpan.Zero, is a sudden change; anything
else is a smooth change over that time, rounded to whole milliseconds, and at least
YeelightLimits.MinTransition (30 milliseconds). A shorter one throws an
ArgumentOutOfRangeException before anything is sent.
await light.SetBrightnessAsync(10); // sudden
await light.SetBrightnessAsync(100, TimeSpan.FromSeconds(3)); // smooth, over three seconds
AdjustBrightnessAsync, AdjustColorTemperatureAsync
and AdjustColorAsync take a duration with the same limit, and without a sudden effect to choose: none or zero is the
shortest time that a light supports (30 milliseconds, which is what is sent), anything else is at least 30 milliseconds. The specification gives a shortest time and no longest one, and a light was not tried here with a long one.
Arguments are checked before anything is sent
An argument that a light would refuse is refused first, with an ArgumentOutOfRangeException that names the argument. Nothing is sent, and
nothing is counted against the limit of commands.
| Argument | Accepted | Constants |
|---|---|---|
| Brightness | 1 to 100 | MinBrightness, MaxBrightness |
| Colour temperature | 1700 to 6500 kelvin | MinColorTemperature, MaxColorTemperature |
| Red, green, blue | 0 to 255 each | MaxRgb is the number that they make, 0xFFFFFF |
| Hue, saturation | 0 to 359, 0 to 100 | MaxHue, MaxSaturation |
| Transition | none or zero, or at least 30 milliseconds | MinTransition |
| Percentage of an adjustment | -100 to 100 | MaxAdjustPercentage |
| Sleep timer | at least one minute | |
| Name | at most 64 bytes in UTF-8 (an ArgumentException) |
An argument that cannot be combined with another is an ArgumentException: a mode other than Normal for switching off, and an
AdjustAction other than Circle for AdjustProperty.Color. The steps of a
colour flow have their own limits: see Colour flows and scenes.
What the light supports
A light lists the methods that it supports in its answer to a search, and refuses any other. The client keeps the list:
Console.WriteLine(string.Join(" ", light.SupportedMethods)); // set_power set_bright ...
if (light.Supports("set_rgb"))
{
await light.SetRgbAsync(0, 128, 128);
}
Console.WriteLine($"colour: {light.SupportsColor}, white: {light.SupportsColorTemperature}");
Console.WriteLine($"background: {light.SupportsBackgroundLight}");
Console.WriteLine($"music mode: {light.SupportsMusicMode}");
SupportedMethodsis the list (read-only), as the protocol names the methods (set_power,set_rgb,bg_set_power, ...).Supportsasks about one.SupportsColor(it listsset_rgborset_hsv),SupportsColorTemperature,SupportsBackgroundLightandSupportsMusicModeanswer the questions that come up most.- A command whose method is not on the list throws a
NotSupportedExceptionbefore anything is sent. - While the list is empty, which is until the light has said what it supports,
Supportsisfalsefor everything, and the commands are not held back: a light that does not know a method answersunsupported method(aYeelightException). - Do not decide what a light can do from its
Model: the specification names five models (mono,color,stripe,ceilingandbslamp) and says that more will follow, and real lights say other things (colora,strip6). The list of methods is what the light itself says it can do.
The state is what the light told
The properties of a light (IsOn, Brightness,
ColorMode, ColorTemperature,
Rgb, Hue, Saturation,
IsFlowing, ...) are what the light told the client. A light reports every change to each connection
that it has, and the client reads all the properties when it connects, so they follow the light, whoever changes it: the Yeelight app, a
remote, the switch on the wall. See Events and state for the events.
A command does not change a property by itself. Its effect arrives as a report from the light, a moment later, and that can be after the command has returned:
await light.SetBrightnessAsync(50);
Console.WriteLine(light.Brightness); // can still be the old value
await light.RefreshAsync(); // one more command: the light says what it is now
Console.WriteLine(light.Brightness);
PropertyChanged tells when the report arrives, without another command: see
Events and state.
A property is 0 (or empty) until the light has reported it, and ColorMode is Rgb until then. A
light that was found by a search has told most of its state in its answer: on or off, brightness, colour mode and the colour values (the
specification lists them in the answer). The rest comes when the client has connected and read the properties. While music mode is on, the
light reports no changes.
Colour mode
A light that can show colours does it in one of three modes, and ColorMode says which. The mode decides which of the
colour properties is what the light shows; the others hold what they had, and mean nothing.
ColorMode |
The light shows | Valid properties |
|---|---|---|
Rgb |
a colour | Rgb, and Color, which is the same colour as a System.Drawing.Color |
ColorTemperature |
a white | ColorTemperature, in kelvin |
Hsv |
a colour | Hue and Saturation, together |
Setting a value switches the light to its mode: SetRgbAsync to Rgb, SetColorTemperatureAsync to ColorTemperature and SetHsvAsync to
Hsv. So read the property that belongs to the mode:
string Describe(YeelightLight light)
{
return light.ColorMode switch
{
ColorMode.ColorTemperature => $"{light.ColorTemperature} K",
ColorMode.Rgb => $"colour #{light.Rgb:X6}",
_ => $"hue {light.Hue}, saturation {light.Saturation}%",
};
}
The background light has its own mode (the protocol calls it bg_lmode).
Commands that need the light to be on
The specification says that these commands are only accepted by a light that is on. A light that is off answers with an error, which the
client throws as a YeelightException of the kind DeviceRejected,
with the message of the light.
| Command | Method of the protocol |
|---|---|
SetBrightnessAsync |
set_bright |
SetColorTemperatureAsync |
set_ct_abx |
SetRgbAsync |
set_rgb |
SetHsvAsync |
set_hsv |
SetDefaultAsync |
set_default |
StartColorFlowAsync |
start_cf |
SetSleepTimerAsync |
cron_add |
The same goes for their twins on the background light: the specification says that those work like the commands of the main light. For the other commands the specification makes no such statement. A scene is the exception on the other side: it is accepted by a light that is on and by one that is off, and switches the light on first. So these do the same:
await light.TurnOnAsync();
await light.SetRgbAsync(255, 140, 0);
// One command: switches the light on when it is off, and sets a colour and a brightness.
await light.SetSceneAsync(YeelightScene.Rgb(255, 140, 0, 60));
Cancelling a command
Every command takes a CancellationToken as its last parameter. Cancelling it ends the wait for the answer, and the command throws an
OperationCanceledException. A command that was already sent is not undone: the light may still carry it out. The connection stays usable.
using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(5));
try
{
await light.SetBrightnessAsync(50, cancellationToken: cancellation.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Stopped waiting.");
}
A command that takes too long on its own is not cancelled, but fails with Timeout: see
Errors and limits.
Several commands at once
Commands can be sent from several threads at the same time. The client writes them one at a time over the connection of the light and matches each answer to its command. They all count against the limit of 60 commands a minute, which the client keeps for you: see Errors and limits.
Any other command
SendCommandAsync sends a command by its method and parameters, as the protocol has them, for
what the typed methods do not cover. Nothing is added to the method and nothing is checked: no support check, no argument check, and no
bg_ prefix (to command the background light with it, name the method bg_set_power yourself). The parameters are numbers, text and truth
values. It returns the answer of the light as a YeelightResult, and does not throw for a command that the
light refused:
var result = await light.SendCommandAsync("get_prop", ["power", "bright"]);
if (result.Success)
{
Console.WriteLine($"power {result.GetString(0)}, brightness {result.GetString(1)}");
}
else
{
Console.WriteLine($"Refused: {result.Error!.Message}");
}
Look at Success, or call EnsureSuccess to get the
exception that a typed command would have thrown. Values holds the values of the answer as JSON elements.