17 May 2026
Planet Grep
Jan De Luyck: Integrating the Ikea Starkvind with Home Assistant using deCONZ
Ikea Starkvind and Home Assistant
We recently bought an Ikea Starkvind Air Purifier, which supports Zigbee. I wanted to find out what I could do with it from within Home Assistant, possibly automating when it runs and when not. I also wanted to add some UI elements, like the mushroom fan card or the air purifier card, both of which rely on there being a fan entity.
Zigbee integration with deCONZ
I already had a ConbeeII Zigbee USB controller in use with the deCONZ Integration. Pairing the Starkvind was a matter of telling the Phoscon Software (which comes with the deCONZ integration) to scan for new sensors and pushing the pairing button on the Starkvind.
Surprisingly enough, only three entities showed up in Home Assistant:
- Air Purifier PM25
- Fan Mode
- Filter Runtime
deCONZ entities exposed through Home Assistant for the Starkvind Air Purifier
When looking in the deCONZ application there were a lot more attributes:
deCONZ cluster information
The deCONZ integration uses a python library for deconz, and in issue #322 I found that only these three items were actually requested to be added. I have since requested some more, but it's uncertain when and if those will be made available.
I came across this blog post by OyWin, detailing how they used the REST sensors to add their Starkvind into Home Assistant. While the approach was definitely the right way to go, I was not a fan of doing so many individual REST calls (one per sensor) as it's not needed - Home Assistant can handle it in 1 call per REST-API target.
deCONZ REST-API
Checking the deCONZ REST-API documentation for the Starkvind, there are a lot more attributes available, published under different devices: ZHAAirPurifier and ZHAParticulateMatter
The ones I wanted were:
ZHAAirPurifier
| Section | Attribute | Exposed via deCONZ integration | R/O or R/W |
|---|---|---|---|
| Config | filterlifetime | x | Read/Write |
| Config | ledindication | x | Read/Write |
| Config | locked | x | Read/Write |
| Config | mode | ✓ | Read Write |
| Config | on | x | Read Only |
| State | deviceruntime | x | Read Only |
| State | filterruntime | ✓ | Read Only |
| State | lastupdated | x | Read Only |
| State | replacefilter | x | Read Only |
| State | speed | x | Read Only |
ZHAParticulateMatter
| Section | Attribute | Exposed via deCONZ integration | R/O or R/W |
|---|---|---|---|
| State | measured_value | ✓ | Read Only |
| State | airquality | x | Read Only |
Time to get those into Home Assistant.
Configuring the deCONZ REST-API port
In order to be able to query the deCONZ REST-API, you need to make sure a port is configured in Home Assistant → Settings → Apps → deCONZ → Configuration
deCONZ App Network Configuration
If this port is set you'll be able to issue http queries to the URL of your Home Assistant installation on the port specified. In my case this is http://home-assistant.internal:40850/
Finding the correct API urls
To find the correct API endpoints to use go to Home Assistant → deCONZ → Phoscon. Open the hamburger menu on the left and pick Help → API Information
Phoscon API Information screen
In the subsequent screen, pick "Sensors" and look for "Ikea STARKVIND Air Purifier". You should find two entries in the dropdown:
Phoscon API Information for the Starkvind Air Purifier
Once you click on one of the sensors, you will get a dump of what the API returns, and on top of that window, the API endpoint URL. In my example this reads:
//home-assistant.internal:8123/api/hassio_ingress/juXMtc1g4Z85iNwXSis58q2z7Kw7XO0Lz5k2X6cBsZ0/api/792DA42905/sensors/93. The converted direct unauthenticated URL becomes http://home-assistant.internal:40850/api/792DA42905/sensors/93.
Phoscon API information for the ZHAAirPurifier entity of the Starkvind Air Purifier
792DA42905 is your own API key, and 93 is the internal numbering of deCONZ for your sensor.
Now, this URL allows you to query the API from the outside. I did not need this as I wanted to run the queries from inside Home Assistant. You can find the internal url by going to Home Assistant → Settings → Devices & services, selecting the deCONZ integration and picking the Conbee2. In the Service Info there is a "Visit" link, which shows you the internal hostname to use.
deCONZ Conbee2 Service Information
This will usually be core-deconz, so the URL becomes http://core-deconz:40850/api/<apikey>/sensors/<sensor-id>.
Home Assistant Configuration
Creating the REST sensors
Using the URL assembled above I added the sensor and binary_sensor entities to Home Assistant.
- rest:
- resource: http://core-deconz:40850/api/792DA42905/sensors/93
binary_sensor:
- name: Ikea Starkvind Led Indication
value_template: "{{ value_json.config.ledindication }}"
unique_id: ikea_starkvind_led_indication
- name: Ikea Starkvind Locked
value_template: "{{ value_json.config.locked }}"
unique_id: ikea_starkvind_locked
- name: Ikea Starkvind Sensor On
value_template: "{{ value_json.config.on }}"
unique_id: ikea_starkvind_sensor_on
- name: Ikea Starkvind Replace Filter
value_template: "{{ value_json.state.replacefilter }}"
unique_id: ikea_starkvind_replace_filter
sensor:
- name: Ikea Starkvind Filter Runtime
value_template: "{{ value_json.state.filterruntime }}"
unique_id: ikea_starkvind_filter_runtime
device_class: duration
unit_of_measurement: min
- name: Ikea Starkvind Device Runtime
value_template: "{{ value_json.state.deviceruntime }}"
unique_id: ikea_starkvind_device_runtime
device_class: duration
unit_of_measurement: min
- name: Ikea Starkvind Filter Lifetime
value_template: "{{ value_json.config.filterlifetime }}"
unique_id: ikea_starkvind_filter_lifetime
device_class: duration
unit_of_measurement: min
- name: Ikea Starkvind Mode
value_template: "{{ value_json.config.mode }}"
unique_id: ikea_starkvind_mode
- name: Ikea Starkvind Fan Speed
value_template: "{{ value_json.state.speed }}"
unique_id: ikea_starkvind_fan_speed
state_class: measurement
- name: Ikea Starkvind Last Updated
value_template: "{{ value_json.state.lastupdated + 'Z' }}"
unique_id: ikea_starkvind_lastupdated
device_class: timestamp
Enabling setting the led indicator
To be able to update the ledindication, I added a binary helper called ikea_starkvind_ledindication as a toggle, a rest_command to set it, and an automation to bind the two together:
rest_command:
ikea_starkvind_set_ledindication:
url: "http://core-deconz:40850/api/792DA42905/sensors/93"
method: put
content_type: "application/json; charset=utf-8"
payload: '{ "config": { "ledindication": {{ states("input_boolean.ikea_starkvind_ledindication") | bool | lower }}}}'
alias: Ikea Starkvind - Sync Led Indication
description: ""
triggers:
- trigger: state
entity_id:
- input_boolean.ikea_starkvind_ledindication
actions:
- action: rest_command.ikea_starkvind_set_ledindication
data: {}
mode: single
Additional sensors
I also added a template sensor to calculate the lifetime left of the filter:
template:
- sensor:
name: Ikea Starkvind Filter Lifetime Remaining
state: "{{ states('sensor.ikea_starkvind_filter_lifetime') | int - states('sensor.ikea_starkvind_filter_runtime') | int }}"
unique_id: ikea_starkvind_filter_lifetime_remaining
device_class: duration
unit_of_measurement: min
Creating a Fan entity
To use the premade cards I needed a fan entity. This can be created as a template, based off of the previously created entities:
- fan:
- name: "IKEA Starkvind"
unique_id: ikea_starkvind_fan
availability: "{{ states('select.ikea_starkvind_fan_mode') not in ['unknown', 'unavailable'] }}"
state: "{{ states('select.ikea_starkvind_fan_mode') != 'off' }}"
percentage: >
{% set map = {
'speed_1': 20, 'speed_2': 40, 'speed_3': 60,
'speed_4': 80, 'speed_5': 100
} %}
{{ map.get(states('select.ikea_starkvind_fan_mode'), 0) }}
preset_mode: >
{% if states('select.ikea_starkvind_fan_mode') == 'auto' %}auto{% endif %}
preset_modes:
- auto
speed_count: 5
turn_on:
action: select.select_option
target:
entity_id: select.ikea_starkvind_fan_mode
data:
option: auto
turn_off:
action: select.select_option
target:
entity_id: select.ikea_starkvind_fan_mode
data:
option: "off"
set_percentage:
action: select.select_option
target:
entity_id: select.ikea_starkvind_fan_mode
data:
option: >
{% if percentage == 0 %} off
{% elif percentage <= 20 %} speed_1
{% elif percentage <= 40 %} speed_2
{% elif percentage <= 60 %} speed_3
{% elif percentage <= 80 %} speed_4
{% else %} speed_5
{% endif %}
set_preset_mode:
action: select.select_option
target:
entity_id: select.ikea_starkvind_fan_mode
data:
option: "{{ preset_mode }}"
Home Assistant Cards
I tried out a few cards to see what I liked.
Custom Purifier Card
My first try was the custom air purifier card with this configuration:
type: custom:purifier-card
entity: fan.ikea_starkvind
aqi:
entity_id: sensor.ikea_starkvind_air_quality_measured_value
unit: μg/m³
stats:
- entity_id: sensor.ikea_starkvind_filter_lifetime_remaining
value_template: "{{ (value | float(0) / 60 / 24 ) | round(1) }}"
unit: days
subtitle: Filter Life Remaining
shortcuts:
- name: Speed 1
icon: mdi:weather-night
percentage: 20
- name: Speed 2
icon: mdi:circle-slice-2
percentage: 40
- name: Speed 3
icon: mdi:circle-slice-4
percentage: 60
- name: Speed 4
icon: mdi:circle-slice-6
percentage: 80
- name: Speed 5
icon: mdi:circle-slice-8
percentage: 100
- name: Auto
icon: mdi:brightness-auto
preset_mode: auto
It ended up looking like this, which did not thrill me.
Air Purifier Card
Custom cards
I cobbled something together based on the mushroom-fan-card, the button-card, the template-entity-row and the lovelace-expander-card.
type: vertical-stack
cards:
- type: custom:mushroom-fan-card
entity: fan.ikea_starkvind
icon_animation: true
primary_info: name
secondary_info: state
show_percentage_control: true
collapsible_controls: true
show_direction_control: false
show_oscillate_control: false
- type: horizontal-stack
cards:
- type: custom:button-card
icon: mdi:circle-outline
entity: fan.ikea_starkvind
show_name: false
aspect_ratio: 1/1
tap_action:
action: call-service
service: fan.set_percentage
data:
percentage: 0
target:
entity_id: fan.ikea_starkvind
styles:
card:
- background-color: |-
[[[
return entity.state === 'off'
? 'rgba(var(--rgb-state-active-color), 0.2)'
: 'var(--ha-card-background)';
]]]
icon:
- color: |-
[[[
return entity.state === 'off'
? 'var(--state-active-color)'
: 'var(--secondary-text-color)';
]]]
- type: custom:button-card
icon: mdi:weather-night
entity: fan.ikea_starkvind
show_name: false
aspect_ratio: 1/1
tap_action:
action: call-service
service: fan.set_percentage
data:
percentage: 20
target:
entity_id: fan.ikea_starkvind
styles:
card:
- background-color: |-
[[[
return entity.state === 'on' && entity.attributes.percentage === 20
? 'rgba(var(--rgb-state-active-color), 0.2)'
: 'var(--ha-card-background)';
]]]
icon:
- color: |-
[[[
return entity.state === 'on' && entity.attributes.percentage === 20
? 'var(--state-active-color)'
: 'var(--secondary-text-color)';
]]]
- type: custom:button-card
icon: mdi:circle-slice-2
entity: fan.ikea_starkvind
show_name: false
aspect_ratio: 1/1
tap_action:
action: call-service
service: fan.set_percentage
data:
percentage: 40
target:
entity_id: fan.ikea_starkvind
styles:
card:
- background-color: |-
[[[
return entity.state === 'on' && entity.attributes.percentage === 40
? 'rgba(var(--rgb-state-active-color), 0.2)'
: 'var(--ha-card-background)';
]]]
icon:
- color: |-
[[[
return entity.state === 'on' && entity.attributes.percentage === 40
? 'var(--state-active-color)'
: 'var(--secondary-text-color)';
]]]
- type: custom:button-card
icon: mdi:circle-slice-4
entity: fan.ikea_starkvind
show_name: false
aspect_ratio: 1/1
tap_action:
action: call-service
service: fan.set_percentage
data:
percentage: 60
target:
entity_id: fan.ikea_starkvind
styles:
card:
- background-color: |-
[[[
return entity.state === 'on' && entity.attributes.percentage === 60
? 'rgba(var(--rgb-state-active-color), 0.2)'
: 'var(--ha-card-background)';
]]]
icon:
- color: |-
[[[
return entity.state === 'on' && entity.attributes.percentage === 60
? 'var(--state-active-color)'
: 'var(--secondary-text-color)';
]]]
- type: custom:button-card
icon: mdi:circle-slice-6
entity: fan.ikea_starkvind
show_name: false
aspect_ratio: 1/1
tap_action:
action: call-service
service: fan.set_percentage
data:
percentage: 80
target:
entity_id: fan.ikea_starkvind
styles:
card:
- background-color: |-
[[[
return entity.state === 'on' && entity.attributes.percentage === 80
? 'rgba(var(--rgb-state-active-color), 0.2)'
: 'var(--ha-card-background)';
]]]
icon:
- color: |-
[[[
return entity.state === 'on' && entity.attributes.percentage === 80
? 'var(--state-active-color)'
: 'var(--secondary-text-color)';
]]]
- type: custom:button-card
icon: mdi:circle-slice-8
entity: fan.ikea_starkvind
show_name: false
aspect_ratio: 1/1
tap_action:
action: call-service
service: fan.set_percentage
data:
percentage: 100
target:
entity_id: fan.ikea_starkvind
styles:
card:
- background-color: |-
[[[
return entity.state === 'on' && entity.attributes.percentage === 100
? 'rgba(var(--rgb-state-active-color), 0.2)'
: 'var(--ha-card-background)';
]]]
icon:
- color: |-
[[[
return entity.state === 'on' && entity.attributes.percentage === 100
? 'var(--state-active-color)'
: 'var(--secondary-text-color)';
]]]
- type: custom:expander-card
title: Details
cards:
- type: entities
entities:
- entity: sensor.ikea_starkvind_last_updated
name: Last Updated
- entity: input_boolean.ikea_starkvind_ledindication
name: Led Indicator
- entity: sensor.ikea_starkvind_air_quality
name: Air Quality Indicator
- type: custom:template-entity-row
state: >-
{{ states('sensor.ikea_starkvind_air_quality_measured_value') }}
µg/m³
name: Air Quality
icon: mdi:bacteria-outline
- entity: binary_sensor.ikea_starkvind_replace_filter
name: Replace Filter
- type: custom:template-entity-row
name: Filter Life Remaining
state: |-
{{ (states('sensor.ikea_starkvind_filter_lifetime_remaining') |
float(0) / 60 / 24)| round(1) }} days
icon: mdi:timelapse
animation: false
clear-children: false
This one I kept :)
Collapsed custom card
Unfolded custom card
17 May 2026 2:04pm GMT
Dries Buytaert: Acquia builds Drupal funding into its partner program

Today Acquia announced something I'm really proud of. We're calling it the Acquia Fair Trade Initiative.
When an Acquia partner closes a deal, 2% of that deal flows directly to the Drupal Association, credited in the partner's name, to fund Drupal's infrastructure and long-term growth.
Imagine an Acquia partner closes a $100,000 Drupal deal with Acquia. $2,000 goes to the Drupal Association, attributed to that partner. The 2% comes from Acquia, not from partner margins, so the partner keeps their full revenue and incentives.
The donation is publicly attributed in the Acquia Partner Portal and counts toward the partner's standing in the Drupal Association's Certified Partner Program. It is recognized as financial support for the Drupal Association, separate from non-financial contributions like code, case studies, or community participation.
Most of all, I like that this program is structural. It is not a one-time gift or sponsorship campaign. It is built into the economics of Acquia's partner program, so Drupal's funding grows automatically as Acquia and its partners grow.
Too often, funding for Open Source projects depends on periodic fundraising or individual goodwill. That can work, but it rarely scales in a predictable way.
Open Source sustainability works best when incentives align. With the Fair Trade Initiative, the Drupal Association receives more predictable funding, partners receive recognition through the Drupal Association's Certified Partner Program, and Acquia invests in the long-term health of the Drupal ecosystem its business depends on. And yes, this also creates more incentive for partners to work with Acquia on Drupal projects. Drupal wins, Acquia's partners win, and Acquia wins too. That is what incentive alignment looks like.
I set a reminder for myself to report back in a year, maybe sooner. I'm curious to see what this model can become.
17 May 2026 2:04pm GMT
Dieter Plaetinck: Open Source Consulting & Advisory
I've been an enthusiastic contributor in the Open Source community for over 25 years. During my career, I've worked with and on Open Source software from multiple angles: as a builder, user, seller, customer, and investor. I've seen projects grow and falter. I've seen how licensing and business decisions can both destroy or boost projects and their communities. Though the biggest killer of promising projects and businesses is probably blind spots (challenges that were not expected or well understood).
I was the first engineering hire when Grafana Labs was founded. I was never a founder, board member, or executive, but I worked directly with its exceptional founders, executives and leaders. It's where I learned how to build Open Source software and enterprise OSS businesses the right way. I learned about productive collaboration with communities, with customers, and with colleagues regardless of which department they're in. I also learned the subtle interdependencies between GTM, engineering, and support, and what it really takes to build, launch, and sell products. It takes many revisions of products, their positioning, and licensing strategy (among others).
After 9 years I left Grafana and started exploring many cool OSS projects and people building them. It turned out I have some relevant experience that is complementary to their own expertise. So I started investing in cool projects and refining my thesis on commercial Open Source models, including Open Core, Source Available, and Fair Source. (see Fair Source: the next best model for commercial open source?)
Cool Open Source Software - and the people that build them - deserve support and funding.
- For funding, sometimes the answer is Venture Capital. Sometimes it's staying independent and bootstrapping, or using donations or endowments (e.g. OSS Pledge, Open Source Endowment, etc)
- For support, sometimes you just need a free friendly chat for advice. Sometimes you need a consultant, or an advisor. I'm happy to help in any way I can.
I don't claim to be the world's expert, but the few startups that worked with me were glad they did, so I decided to launch a consulting business. If this sounds interesting, check out the Consulting/advisory page for more details.
17 May 2026 2:04pm GMT