Skip to main content

dirigera

Tests PyPI Python versions Downloads

A Python client for the IKEA DIRIGERA smart home hub. Control lights, outlets, blinds, air purifiers and scenes, read sensors, and react to events, all through the hub's local API without the IKEA cloud.

import dirigera

dirigera_hub = dirigera.Hub(token="<your token>", ip_address="192.168.1.20")

lamp = dirigera_hub.get_light_by_name(lamp_name="Desk lamp")
lamp.set_light(lamp_on=True)
lamp.set_light_level(light_level=60)

for sensor in dirigera_hub.get_environment_sensors():
    print(sensor.attributes.custom_name, sensor.attributes.current_temperature)

This is an unofficial project and not affiliated with IKEA. It is based on the hub's undocumented local API, so firmware updates can change its behaviour.

Contents

Installation

pip install dirigera

The library is tested on Python 3.8 to 3.14.

Getting started

  1. Find the IP address of your hub, for example in your router's list of connected devices. The hub also announces itself on the local network as gw2-<id>.local.

  2. Generate a token. The package installs a generate-token command:

    generate-token 192.168.1.20
    

    When asked, press the action button on the bottom of the hub, then press ENTER. The token is printed to the console. You only need to do this once.

  3. Connect to the hub with the token and IP address:

    import dirigera
    
    dirigera_hub = dirigera.Hub(token="<your token>", ip_address="192.168.1.20")
    
    for light in dirigera_hub.get_lights():
        print(light.attributes.custom_name, light.attributes.is_on)
    

The token gives full control over your hub, so keep it out of your source code, for example by reading it from an environment variable.

Supported devices

Device type Fetch with Known to work with
Lights get_lights() TRADFRI bulbs, KAJPLATS, ORMANAS LED strip
Outlets get_outlets() TRADFRI control outlet, TRETAKT, INSPELNING, GRILLPLATS
Electrical sensors get_electrical_sensors() GRILLPLATS
Blinds get_blinds() FYRTUR
Air purifiers get_air_purifiers() STARKVIND
Controllers get_controllers() STYRBAR, RODRET, SOMRIG, TRADFRI on/off switch
Environment sensors get_environment_sensors() VINDSTYRKA, ALPSTUGA
Motion sensors get_motion_sensors() TRADFRI motion sensor, VALLHORN
Light sensors get_light_sensors() MYGGSPRAY, VALLHORN
Occupancy sensors get_occupancy_sensors() MYGGSPRAY
Open/close sensors get_open_close_sensors() –
Water sensors get_water_sensors() BADRING

Devices from other manufacturers that are paired with the hub often work as well, as long as the hub reports them as one of these device types. If a device is missing or fails to load, please open an issue and include its data from dirigera_hub.get("/devices").

Usage

All examples assume a connected hub:

import dirigera

dirigera_hub = dirigera.Hub(token="<your token>", ip_address="192.168.1.20")

Finding devices

Every device type can be fetched as a list, by its name from the IKEA app, or by its id:

lights = dirigera_hub.get_lights()
light = dirigera_hub.get_light_by_name(lamp_name="Desk lamp")
light = dirigera_hub.get_light_by_id(id_=light.id)

devices = dirigera_hub.get_all_devices()  # all supported devices

The hub doesn't return devices in a fixed order, so look devices up by name or id rather than by their position in a list. Lookups by name raise an AssertionError if no device has that name. Lookups by id raise a ValueError if the id doesn't exist or belongs to another device type.

All getters
Device type All By name By id
Lights get_lights() get_light_by_name(lamp_name=...) get_light_by_id(id_=...)
Outlets get_outlets() get_outlet_by_name(outlet_name=...) get_outlet_by_id(id_=...)
Electrical sensors get_electrical_sensors() – get_electrical_sensor_by_id(id_=...)
Blinds get_blinds() get_blind_by_name(blind_name=...) get_blinds_by_id(id_=...)
Air purifiers get_air_purifiers() get_air_purifier_by_name(air_purifier_name=...) get_air_purifier_by_id(id_=...)
Controllers get_controllers() get_controller_by_name(controller_name=...) get_controller_by_id(id_=...)
Environment sensors get_environment_sensors() get_environment_sensor_by_name(environment_sensor_name=...) get_environment_sensor_by_id(id_=...)
Motion sensors get_motion_sensors() get_motion_sensor_by_name(motion_sensor_name=...) get_motion_sensor_by_id(id_=...)
Light sensors get_light_sensors() get_light_sensor_by_name(light_sensor_name=...) get_light_sensor_by_id(id_=...)
Occupancy sensors get_occupancy_sensors() get_occupancy_sensor_by_name(occupancy_sensor_name=...) get_occupancy_sensor_by_id(id_=...)
Open/close sensors get_open_close_sensors() get_open_close_sensor_by_name(open_close_sensor_name=...) get_open_close_by_id(id_=...)
Water sensors get_water_sensors() get_water_sensor_by_name(water_sensor_name=...) get_water_sensor_by_id(id_=...)
Scenes get_scenes() get_scene_by_name(scene_name=...) get_scene_by_id(scene_id=...)
Adaptive profiles get_adaptive_profiles() – –
All devices get_all_devices() – –

Working with devices

Each device is a Python object with these fields:

Field Description
id Unique id of the device
relation_id Shared by devices that belong to the same physical product (see below)
type, device_type Device type as reported by the hub, e.g. "light"
is_reachable Whether the hub can currently reach the device
last_seen, created_at Timestamps
room Room from the IKEA app (id, name, color, icon), if assigned
capabilities can_send and can_receive: the attributes the device sends or accepts
attributes The device state, e.g. is_on or current_temperature (listed per device type below), plus custom_name, model, manufacturer, firmware_version, hardware_version, serial_number, product_code and firmware update (ota_*) fields

Attribute names are the hub's names in snake_case: the hub's lightLevel is light_level.

The setter methods send the change to the hub and update the object. Most of them first check the device's capabilities and the allowed value range, and raise an AssertionError or ValueError without sending anything if the change isn't supported. To get the latest state from the hub, call reload(). It updates the object in place:

light = dirigera_hub.get_light_by_name(lamp_name="Desk lamp")
light.reload()
print(light.attributes.is_on)

Some products appear as several devices that share the same relation_id. The VALLHORN, for example, is a motion sensor and a light sensor, and the GRILLPLATS plug is an outlet and an electrical sensor.

Lights

Attribute Description
is_on Whether the light is on
light_level Brightness, 1–100
color_temperature Color temperature in Kelvin
color_temperature_min, color_temperature_max Supported color temperature range, as reported by the bulb
color_hue, color_saturation Color, hue 0–360 and saturation 0–1
startup_on_off Behaviour after a power outage (StartupEnum)
from dirigera.devices.device import StartupEnum

light = dirigera_hub.get_light_by_name(lamp_name="Desk lamp")

light.set_light(lamp_on=True)
light.set_light_level(light_level=60)
light.set_color_temperature(color_temp=2700)  # Kelvin
light.set_light_color(hue=30, saturation=0.8)
light.set_startup_behaviour(behaviour=StartupEnum.START_PREVIOUS)
light.set_name(name="Reading lamp")

set_color_temperature() checks the value against the range the bulb reports. Some bulbs report this range in mireds instead of Kelvin; the library converts it, so always pass Kelvin.

StartupEnum controls what happens when power returns after an outage: START_ON, START_OFF, START_PREVIOUS (restore the previous state) or START_TOGGLE (switching the power off and on again toggles the light).

Adaptive lighting

Adaptive profiles are created in the IKEA app and define light levels and color temperatures over the day. A light that follows a profile is adjusted by the hub automatically, which interpolates between the entries of the schedule.

profile = dirigera_hub.get_adaptive_profiles()[0]
for entry in profile.adaptive_schedule:
    print(entry.start_time, entry.light_level, entry.color_temperature)

light.activate_adaptive_profile(profile_id=profile.id)

Changing a light manually, for example by setting its light level, takes it out of adaptive mode. light.adaptive_profile.active shows whether a light currently follows a profile (call light.reload() first to get the current state).

Outlets

Attribute Description
is_on Whether the outlet is switched on
startup_on_off Behaviour after a power outage (StartupEnum)
status_light Whether the status light is on
current_active_power Current power in W
current_amps, current_voltage Current in A and voltage in V
total_energy_consumed Total energy in kWh
energy_consumed_at_last_reset, time_of_last_energy_reset, total_energy_consumed_last_updated Energy counter details

The energy values are only available on outlets with energy metering, such as INSPELNING.

from dirigera.devices.device import StartupEnum

outlet = dirigera_hub.get_outlet_by_name(outlet_name="Coffee machine")

outlet.set_on(outlet_on=True)
outlet.set_startup_behaviour(behaviour=StartupEnum.START_OFF)
outlet.set_name(name="Espresso machine")

Electrical sensors

Some plugs, such as GRILLPLATS, report their energy values as a separate electrical sensor instead of on the outlet. It has the same energy attributes as outlets (current_active_power, current_amps, current_voltage, total_energy_consumed, ...). The sensor and the outlet share the same relation_id:

outlet = dirigera_hub.get_outlet_by_name(outlet_name="Grill")
electrical_sensor = next(
    sensor
    for sensor in dirigera_hub.get_electrical_sensors()
    if sensor.relation_id == outlet.relation_id
)
print(electrical_sensor.attributes.current_active_power)

Blinds

Attribute Description
blinds_current_level Current position, 0–100
blinds_target_level Position the blind is moving to, 0–100
blinds_state "up", "down" or "stopped"
battery_percentage Battery level
from dirigera.devices.blinds import BlindsStateEnum

blind = dirigera_hub.get_blind_by_name(blind_name="Bedroom blind")

blind.set_target_level(target_level=50)
blind.set_state(state=BlindsStateEnum.STOPPED)  # or UP / DOWN
blind.set_name(name="Window blind")

Air purifiers

Attribute Description
fan_mode FanModeEnum: OFF, ON, LOW, MEDIUM, HIGH or AUTO
motor_state Fan speed, 0–50
current_p_m25 Particulate matter (PM2.5)
child_lock, status_light Whether the child lock and status light are on
filter_alarm_status, filter_elapsed_time, filter_lifetime Filter state
motor_runtime, fan_mode_sequence Further fan details
from dirigera.devices.air_purifier import FanModeEnum

air_purifier = dirigera_hub.get_air_purifier_by_name(air_purifier_name="Living room purifier")

air_purifier.set_fan_mode(fan_mode=FanModeEnum.AUTO)
air_purifier.set_motor_state(motor_state=30)  # 0 = off, 1 = auto, up to 50
air_purifier.set_child_lock(child_lock=True)
air_purifier.set_status_light(light_state=False)
air_purifier.set_name(name="Bedroom purifier")

Controllers

Remotes (device_type "lightController") and shortcut buttons such as SOMRIG ("shortcutController") have the attributes battery_percentage, is_on and switch_label, and support reload() and set_name().

Button presses are not stored as device state. To react to them, use the event listener or create a scene with a controller trigger.

Sensors

All sensors support reload() and set_name(name=...). Battery powered sensors have a battery_percentage attribute.

Sensor Fetch with Attributes
Environment sensor get_environment_sensors() current_temperature (°C), current_r_h (relative humidity in %), current_p_m25, max_measured_p_m25, min_measured_p_m25, voc_index (VINDSTYRKA), current_c_o2 (ppm, ALPSTUGA)
Motion sensor get_motion_sensors() is_detected, is_on, light_level
Light sensor get_light_sensors() illuminance, min_illuminance, max_illuminance
Occupancy sensor get_occupancy_sensors() is_detected
Open/close sensor get_open_close_sensors() is_open
Water sensor get_water_sensors() water_leak_detected
door = dirigera_hub.get_open_close_sensor_by_name(open_close_sensor_name="Front door")
print("open" if door.attributes.is_open else "closed")

for sensor in dirigera_hub.get_water_sensors():
    if sensor.attributes.water_leak_detected:
        print(f"Leak at {sensor.attributes.custom_name}!")

Scenes

scene = dirigera_hub.get_scene_by_name(scene_name="Movie night")

scene.trigger()  # run the scene
scene.undo()     # undo it again shortly afterwards
dirigera_hub.delete_scene(scene_id=scene.id)

A scene has an info (name and icon), a list of triggers that start it and a list of actions it performs.

Creating scenes

create_scene() creates a scene and returns it. Without triggers, the scene can be run from the app or with scene.trigger().

The scene models use the hub's camelCase names when you create them, for example clickPattern=..., while their fields are read in snake_case, for example trigger.click_pattern.

A scene that turns a light off:

from dirigera.devices.scene import Action, ActionAttributes, Icon, Info

light = dirigera_hub.get_light_by_name(lamp_name="Desk lamp")

scene = dirigera_hub.create_scene(
    info=Info(name="Desk lamp off", icon=Icon.SCENES_BOOK),
    actions=[
        Action(id=light.id, type="device", enabled=True, attributes=ActionAttributes(is_on=False)),
    ],
)

A scene that runs when a shortcut button is pressed:

from dirigera.devices.scene import ClickPattern, ControllerType, Icon, Info, Trigger, TriggerDetails

button = dirigera_hub.get_controller_by_name(controller_name="Shortcut button")

scene = dirigera_hub.create_scene(
    info=Info(name="Button scene", icon=Icon.SCENES_HEART),
    triggers=[
        Trigger(
            type="controller",
            disabled=False,
            trigger=TriggerDetails(
                controllerType=ControllerType.SHORTCUT_CONTROLLER,
                clickPattern=ClickPattern.SINGLE_PRESS,
                buttonIndex=0,
                deviceId=button.id,
            ),
        ),
    ],
)

A scene that runs every evening at 22:00 and ends after one hour:

from dirigera.devices.scene import EndTriggerEvent, Icon, Info, Trigger, TriggerDetails

scene = dirigera_hub.create_scene(
    info=Info(name="Evening", icon=Icon.SCENES_MOON),
    triggers=[
        Trigger(
            type="time",
            disabled=False,
            trigger=TriggerDetails(days=["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], time="22:00"),
            endTriggerEvent=EndTriggerEvent(type="duration", trigger=TriggerDetails(duration=3600)),
        ),
    ],
)

All icons are listed in the Icon enum.

Listening to events

The hub publishes an event whenever something changes, for example when a light is switched or a sensor detects motion. create_event_listener() connects to the hub and calls your functions for each event. Each message is a JSON string with the event type (for example deviceStateChanged, sceneCreated, sceneUpdated or sceneDeleted) and its data, which includes the id of the device or scene:

import json

light = dirigera_hub.get_light_by_name(lamp_name="Desk lamp")

def on_message(ws, message: str):
    event = json.loads(message)
    if event["data"].get("id") == light.id:
        print(event["type"], event["data"].get("attributes"))

def on_error(ws, error):
    print(error)

dirigera_hub.create_event_listener(on_message=on_message, on_error=on_error)
deviceStateChanged {'isOn': False}

create_event_listener() blocks until the connection is closed. Call dirigera_hub.stop_event_listener(), for example from another thread, to close it. It also accepts on_open, on_close and the other callbacks of websocket-client's WebSocketApp.

Reconnecting

The hub sometimes closes the connection on its own (status 1000, "disconnected due to inactivity."), and create_event_listener() then returns. The reconnect argument doesn't cover this, as it only reconnects after connection errors. To keep listening, call it in a loop:

import time

running = True

def stop_listening():
    # create_event_listener() blocks, so call this from another thread
    global running
    running = False
    dirigera_hub.stop_event_listener()

while running:
    dirigera_hub.create_event_listener(on_message=on_message, on_error=on_error)
    time.sleep(5)  # short pause before listening again

Events that happen while the listener reconnects are missed. If you need the current state, fetch it again after reconnecting, for example in on_open.

Raw API access

For anything the library doesn't cover yet, you can send requests to the hub's API directly. The hub responds with the raw JSON data:

devices = dirigera_hub.get("/devices")

light = dirigera_hub.get_light_by_name(lamp_name="Desk lamp")
dirigera_hub.patch(route=f"/devices/{light.id}", data=[{"attributes": {"lightLevel": 30}}])

post() and delete() work the same way.

Troubleshooting

generate-token times out

Check the MTU of the network interface that connects to the hub. A mismatch, such as jumbo frames (MTU 9000) on your computer while the hub uses 1500, makes the connection time out. Setting the interface to an MTU of 1500 fixes it.

The token stopped working

Generate a new token with generate-token, for example after a factory reset of the hub.

Loading devices fails with a ValidationError, or a device is missing

The device reports data that the library doesn't expect yet, or its device type isn't supported. Please open an issue with the device's data, which you get with dirigera_hub.get("/devices"). Remove the serial number if you like.

A setter raises AssertionError: ... does not support ...

The device doesn't accept this attribute. device.capabilities.can_receive lists what it does accept.

The event listener stops after a while

The hub closes idle connections on its own. See Reconnecting.

Why is the hub's certificate not verified?

The hub uses a self-signed certificate, so the library connects without verifying it.

Contributing

Contributions are welcome: open an issue for bugs and device support, or send a pull request.

To set up a development environment, run setup.sh. It creates a virtual environment and installs requirements.txt and dev-requirements.txt. Then:

  • run-test.sh or pytest runs the tests
  • run-pylint.sh runs the linter
  • run-mypy.sh runs the type checks
  • run-python-versions-test.sh runs the tests on all supported Python versions (requires Docker)

The same checks run automatically on every pull request.

License

MIT

Metadata

Release files for dirigera 1.4.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dirigera 1.4.1
File Size Uploaded
dirigera-1.4.1.tar.gz 44.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for dirigera 1.4.1
File Interpreter ABI Platform
dirigera-1.4.1-py3-none-any.whl Python 3 none any Details

Total release size: 75.3 kB

Release files / dirigera-1.4.1.tar.gz

Download URL dirigera-1.4.1.tar.gz
Size 44.0 kB
Tags Source
SHA-256 checksum
How to use checksums
31de8170b222792728e2c1d3557b97a99b311f1490ed03865e66ea210ea20292
BLAKE2b-256 checksum
How to use checksums
338ef9f542d90d5f5a9f3028df05060c51e942df03f0c29b4e7ce2437f19db39
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / dirigera-1.4.1-py3-none-any.whl

Download URL dirigera-1.4.1-py3-none-any.whl
Size 31.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e44387ea872433440a49f8785745d1869b91c2c26710ceadf06f794fe567c3d8
BLAKE2b-256 checksum
How to use checksums
f9f3edffc8d503f62427e289e92436f450317c5b278331474cd034d0c3a2fff5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

This release

1.4.1 This release

2 release files

1.4.0

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.2.7

2 release files

1.2.6

2 release files

1.2.5

2 release files

1.2.4

2 release files

1.2.3

2 release files

1.2.2

2 release files

1.2.1

2 release files

1.2.0

2 release files

1.1.9

2 release files

1.1.8

2 release files

1.1.7

2 release files

1.1.6

2 release files

1.1.5

2 release files

1.1.4

2 release files

1.1.3

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.12

2 release files

1.0.11

2 release files

1.0.10

2 release files

1.0.9

2 release files

1.0.8

2 release files

1.0.7

2 release files

1.0.6

2 release files

1.0.5

2 release files

1.0.4

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.1.11

2 release files

0.1.10

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page