DuckTyping.Ikea.Dirigera
1.2.0
Prefix Reserved
dotnet add package DuckTyping.Ikea.Dirigera --version 1.2.0
NuGet\Install-Package DuckTyping.Ikea.Dirigera -Version 1.2.0
<PackageReference Include="DuckTyping.Ikea.Dirigera" Version="1.2.0" />
<PackageVersion Include="DuckTyping.Ikea.Dirigera" Version="1.2.0" />
<PackageReference Include="DuckTyping.Ikea.Dirigera" />
paket add DuckTyping.Ikea.Dirigera --version 1.2.0
#r "nuget: DuckTyping.Ikea.Dirigera, 1.2.0"
#:package DuckTyping.Ikea.Dirigera@1.2.0
#addin nuget:?package=DuckTyping.Ikea.Dirigera&version=1.2.0
#tool nuget:?package=DuckTyping.Ikea.Dirigera&version=1.2.0
IKEA DIRIGERA Hub client library
| ❗ This is an unofficial library and is not affiliated with or endorsed by IKEA. |
|---|
An unofficial client library for the IKEA DIRIGERA smart home hub.
Connects to the API via the local network, handles OAuth2 PKCE pairing, allows for controlling lights, and streams real-time device readings over WebSocket.
Installation
Install via the terminal or the "Manage NuGet Packages" in your IDE of choice.
dotnet add package DuckTyping.Ikea.Dirigera
First-time pairing
The hub uses an OAuth2 PKCE flow that requires a one-time physical button press on the DIRIGERA hub. When you start the application for the first time (or after a token expires) you will see:
Press the Action button on the DIRIGERA hub (short press). Waiting 60s...
Press the small button on the bottom of the hub within 60 seconds. The token is then saved to disk and reused on all subsequent starts (no button press needed until the token expires).
The default token file location is ~/.dirigera-tokens.json. You can override this with StorePath in the settings (see below).
Setup with dependency injection
Register the client in your Program.cs or wherever you configure your IServiceCollection:
builder.Services.AddDirigeraClient(settings =>
{
settings.Ip = "192.168.1.100"; // local IP address of your DIRIGERA hub
});
To find the hub IP, check your router's DHCP leases or look for a device named DIRIGERA on your network.
Optional settings
builder.Services.AddDirigeraClient(settings =>
{
settings.Ip = "192.168.1.100";
settings.StorePath = "/var/myapp/dirigera-tokens.json"; // custom token file location
});
| Setting | Default | Description |
|---|---|---|
Ip |
(required) | Local IP address of the DIRIGERA hub |
StorePath |
~/.dirigera-tokens.json |
Path where the OAuth token is persisted |
Listening for sensor readings
Inject IDirigeraClient and call ListenAsync. It returns an IAsyncEnumerable<SensorReading> that you consume with await foreach. The connection and pairing happen automatically on the first call.
await foreach (var reading in client.ListenAsync(cancellationToken))
{
switch (reading)
{
case EnvironmentReading env:
Console.WriteLine($"{env.Name} Temp={env.CurrentTemperature}°C RH={env.CurrentRH}% CO2={env.CurrentCO2}ppm");
break;
case DoorSensorReading door:
Console.WriteLine($"{door.Name} is {(door.IsOpen ? "open" : "closed")}");
break;
case MotionSensorReading motion:
Console.WriteLine($"{motion.Name}: {(motion.IsDetected ? "motion detected" : "clear")}");
break;
case WaterSensorReading water:
Console.WriteLine($"{water.Name}: {(water.IsLeaking ? "LEAK DETECTED" : "dry")}");
break;
}
}
ListenAsync reconnects automatically with exponential backoff if the hub becomes unreachable, and stops cleanly when the CancellationToken is cancelled.
Reading types
All readings inherit from SensorReading which exposes:
| Property | Type | Description |
|---|---|---|
DeviceId |
string |
Unique device identifier from the hub |
Name |
string? |
Custom name set in the IKEA Home app |
Time |
DateTimeOffset |
Timestamp of the reading |
Pattern match on the concrete type to access sensor-specific properties:
DoorSensorReading
| Property | Type | Description |
|---|---|---|
IsOpen |
bool |
true when the door or window is open |
Battery |
int? |
Battery level in percent |
MotionSensorReading
| Property | Type | Description |
|---|---|---|
IsDetected |
bool |
true when motion or presence is detected |
Battery |
int? |
Battery level in percent |
WaterSensorReading
| Property | Type | Description |
|---|---|---|
IsLeaking |
bool |
true when water or moisture is detected |
Battery |
int? |
Battery level in percent |
EnvironmentReading
| Property | Type | Description |
|---|---|---|
CurrentTemperature |
double? |
Temperature in °C |
CurrentRH |
int? |
Relative humidity in % |
CurrentCO2 |
int? |
CO₂ concentration in ppm |
CurrentPM25 |
double? |
Particulate matter (PM2.5) in µg/m³ |
Battery |
int? |
Battery level in percent |
Controlling lights
Discover device IDs
Before controlling a light you need its device ID. Call GetDevicesAsync to list everything registered on the hub:
var devices = await client.GetDevicesAsync();
foreach (var device in devices)
Console.WriteLine($"{device.Id} {device.Name} ({device.DeviceType})");
Copy the ID of the light you want to control (e.g. 2d3d4c0a-4624-45d1-bb3f-4f94b569ce48_11) and use it in the calls below.
On / Off / Toggle
await client.TurnOnAsync(lightId);
await client.TurnOffAsync(lightId);
await client.ToggleAsync(lightId); // reads current state, then flips it
To turn a light on and set brightness in a single command, pass the brightness level as the second argument. Pass null to turn on without changing the current brightness:
await client.TurnOnAsync(lightId, brightness: 75); // turn on at 75%
await client.TurnOnAsync(lightId, brightness: null); // turn on, keep current brightness
Dimming
Brightness is a value from 1 (minimum) to 100 (maximum).
await client.DimAsync(lightId, brightness: 50);
Color temperature (white spectrum)
Color temperature is in Kelvin. Typical range for IKEA bulbs is 2200 K (warm white) to 4000 K (cool white).
await client.SetColorTemperatureAsync(lightId, kelvin: 2700);
Color (RGB)
Pass standard RGB values (0–255 per channel). The library converts them to the hue/saturation format the hub expects.
await client.SetColorAsync(lightId, r: 255, g: 120, b: 0); // warm orange
To switch back to white after using color mode, call SetColorTemperatureAsync.
Reading light state
All light control methods return a LightStatus reflecting the target state of the command — not a live re-poll of the hub. The physical device may still be transitioning (e.g. dimming or changing color) when the method returns. To read the actual current state without issuing a command, use GetLightStatusAsync:
var status = await client.GetLightStatusAsync(lightId);
Console.WriteLine($"On={status.IsOn} Brightness={status.Brightness}% Mode={status.ColorMode}");
LightStatus properties
| Property | Type | Description |
|---|---|---|
DeviceId |
string |
Unique device identifier |
Name |
string? |
Custom name set in the IKEA Home app |
IsOn |
bool |
true if the light is currently on |
Brightness |
int? |
Current brightness 1–100, or null if not dimmable |
ColorMode |
LightColorMode |
Color, Temperature, or Unknown |
ColorTemperature |
int? |
Color temperature in Kelvin (populated when ColorMode is Temperature) |
Hue |
double? |
Hue in degrees 0–360 (populated when ColorMode is Color) |
Saturation |
double? |
Saturation 0–100 (populated when ColorMode is Color) |
IsReachable |
bool |
true if the hub can currently reach the device |
Exceptions
| Exception | When thrown |
|---|---|
DirigeraDeviceNotFoundException |
The device ID does not exist on the hub |
DirigeraValidationException |
A parameter was out of range (e.g. brightness outside 1–100) |
DirigeraException |
Any other hub error |
Controlling outlets
On / Off / Toggle
await client.TurnOutletOnAsync(outletId);
await client.TurnOutletOffAsync(outletId);
await client.ToggleOutletAsync(outletId); // reads current state, then flips it
Reading outlet state
All outlet methods return an OutletStatus. You can also read it without making any change:
var status = await client.GetOutletStatusAsync(outletId);
Console.WriteLine($"On={status.IsOn} Reachable={status.IsReachable}");
OutletStatus properties
| Property | Type | Description |
|---|---|---|
DeviceId |
string |
Unique device identifier |
Name |
string? |
Custom name set in the IKEA Home app |
IsOn |
bool |
true if the outlet is currently on |
IsReachable |
bool |
true if the hub can currently reach the device |
Controlling blinds
Open / Close / Set position
await client.OpenBlindAsync(blindId); // sets position to 0 (fully open)
await client.CloseBlindAsync(blindId); // sets position to 100 (fully closed)
await client.SetBlindPositionAsync(blindId, 50); // 0 = fully open, 100 = fully closed
Reading blind state
All blind methods return a BlindStatus. You can also read it without making any change:
var status = await client.GetBlindStatusAsync(blindId);
Console.WriteLine($"Current={status.CurrentLevel} Target={status.TargetLevel}");
BlindStatus properties
| Property | Type | Description |
|---|---|---|
DeviceId |
string |
Unique device identifier |
Name |
string? |
Custom name set in the IKEA Home app |
CurrentLevel |
int |
Current position: 0 = fully open, 100 = fully closed |
TargetLevel |
int? |
Position the motor is moving towards, or null if idle |
IsReachable |
bool |
true if the hub can currently reach the device |
Examples
Below are some examples of how to use the library.
Note; these are just example snippets and not full applications ready for production
Example: .NET Console application with lights control
A console application that lists all devices and exposes light control as CLI commands.
using DuckTyping.Ikea.Dirigera;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddDirigeraClient(settings =>
{
settings.Ip = "192.168.1.100";
});
})
.Build();
var client = host.Services.GetRequiredService<IDirigeraClient>();
switch (args)
{
case ["devices"]:
var devices = await client.GetDevicesAsync();
foreach (var d in devices)
Console.WriteLine($"{d.Id} {d.Name,-30} {d.DeviceType}");
break;
case ["lights", "on", var id]:
var on = await client.TurnOnAsync(id);
Console.WriteLine($"{on.Name} is now on at {on.Brightness}%");
break;
case ["lights", "on", var id, var level] when int.TryParse(level, out var onBrightness):
var onDimmed = await client.TurnOnAsync(id, onBrightness);
Console.WriteLine($"{onDimmed.Name} is now on at {onDimmed.Brightness}%");
break;
case ["lights", "off", var id]:
var off = await client.TurnOffAsync(id);
Console.WriteLine($"{off.Name} is now off");
break;
case ["lights", "toggle", var id]:
var toggled = await client.ToggleAsync(id);
Console.WriteLine($"{toggled.Name} is now {(toggled.IsOn ? "on" : "off")}");
break;
case ["lights", "dim", var id, var level] when int.TryParse(level, out var brightness):
var dimmed = await client.DimAsync(id, brightness);
Console.WriteLine($"{dimmed.Name} brightness set to {dimmed.Brightness}%");
break;
case ["lights", "temp", var id, var kelvin] when int.TryParse(kelvin, out var k):
var temp = await client.SetColorTemperatureAsync(id, k);
Console.WriteLine($"{temp.Name} color temperature set to {temp.ColorTemperature}K");
break;
case ["lights", "color", var id, var r, var g, var b]
when int.TryParse(r, out var rv) && int.TryParse(g, out var gv) && int.TryParse(b, out var bv):
var colored = await client.SetColorAsync(id, rv, gv, bv);
Console.WriteLine($"{colored.Name} color set Hue={colored.Hue} Sat={colored.Saturation}%");
break;
case ["lights", "status", var id]:
var status = await client.GetLightStatusAsync(id);
Console.WriteLine($"{status.Name} On={status.IsOn} Brightness={status.Brightness}% Mode={status.ColorMode}");
break;
default:
Console.WriteLine("""
Usage:
devices
lights status <id>
lights on <id>
lights on <id> <1-100>
lights off <id>
lights toggle <id>
lights dim <id> <1-100>
lights temp <id> <kelvin>
lights color <id> <r> <g> <b>
""");
break;
}
Run it from the terminal:
dotnet run -- devices
dotnet run -- lights on 2d3d4c0a-4624-45d1-bb3f-4f94b569ce48_11
dotnet run -- lights on 2d3d4c0a-4624-45d1-bb3f-4f94b569ce48_11 75
dotnet run -- lights dim 2d3d4c0a-4624-45d1-bb3f-4f94b569ce48_11 40
dotnet run -- lights temp 2d3d4c0a-4624-45d1-bb3f-4f94b569ce48_11 2700
dotnet run -- lights color 2d3d4c0a-4624-45d1-bb3f-4f94b569ce48_11 255 120 0
Example: .NET Worker Service
Worker service that listens to events from the DIRIGERA Hub, the worker logs information based on the type of sensor reading that is received.
Program.cs
using DuckTyping.Ikea.Dirigera;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddDirigeraClient(settings =>
{
settings.Ip = builder.Configuration.GetRequiredSection("Dirigera:Ip").Value!;
});
builder.Services.AddHostedService<DirigeraWorker>();
var host = builder.Build();
host.Run();
appsettings.json
{
"Dirigera": {
"Ip": "192.168.1.100"
}
}
DirigeraWorker.cs
public sealed class DirigeraWorker(IDirigeraClient client, ILogger<DirigeraWorker> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
logger.LogInformation("Connecting to DIRIGERA hub...");
await foreach (var reading in client.ListenAsync(stoppingToken))
{
switch (reading)
{
case EnvironmentReading env:
logger.LogInformation(
"[Environment] {Name} Temp={Temp} RH={RH}% CO2={CO2}ppm",
env.Name, env.CurrentTemperature, env.CurrentRH, env.CurrentCO2);
break;
case DoorSensorReading door:
logger.LogInformation(
"[Door] {Name} {State} Bat={Battery}%",
door.Name, door.IsOpen ? "Open" : "Closed", door.Battery);
break;
case MotionSensorReading motion:
logger.LogInformation(
"[Motion] {Name} {State} Bat={Battery}%",
motion.Name, motion.IsDetected ? "Motion" : "Clear", motion.Battery);
break;
}
}
logger.LogInformation("Event loop stopped.");
}
}
Example: Console application
A console application that listens to events from the DIRIGERA Hub and prints info to the console
using DuckTyping.Ikea.Dirigera;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddDirigeraClient(settings =>
{
settings.Ip = "192.168.1.100";
});
})
.Build();
var client = host.Services.GetRequiredService<IDirigeraClient>();
using var cts = new CancellationTokenSource();
Console.CancelKeyPress += (_, e) =>
{
e.Cancel = true;
cts.Cancel();
};
await foreach (var reading in client.ListenAsync(cts.Token))
{
Console.WriteLine($"{reading.Time:HH:mm:ss} {reading.Name ?? reading.DeviceId} {reading.GetType().Name}");
}
Notes
- The hub uses a self-signed TLS certificate on port 8443. The library accepts this certificate automatically. DO NOT expose this port to the internet.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Options (>= 9.0.0)
-
net9.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Options (>= 9.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
- Changed the library status output to use the stderr/exceptions and not stdout to not pollute the output
- Fixed bug in the connect to Hub method
- Fixed bug in patch request to hub
- Fixed issue with when patching a light, the new status is incorrect
- Updated the lights "on" command to include -l (dim level of the light)