-
Notifications
You must be signed in to change notification settings - Fork 698
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Device Support Request] Ikea Starkvind Air Purifier #1215
Comments
If you run |
@Adminiuga please see the logs I've pasted in this gist: https://gist.github.com/kcleong/e2f4f3a2b57f6115e613b479f9257a3e |
I wasn't looking for the device join logs. Need to see what information device sends when idle. Join it and leave running for 24 hours. Send the logs with all the traffic from the device |
I also own such a device now and can provide additional logs if needed @Adminiuga |
here's some detailed info: dresden-elektronik/deconz-rest-plugin#5351 (comment) |
I also own the device and would love to see support. Especially the raw pm25 and fan_speeds / adjustments. Thank you!! |
I can also help for integration (no experience). |
I would love to help as well. I bought a Starkvind a few days ago and am able to control the fan with ZHA. It seems like all the functionality is integrated in Zigbee2MQTT and i was thinking, isn't there a guide on how to convert a Zigbee2MQTT integration to ZHA integration? It's been a few years since i last touched python so i need to refresh my memory a bit but i'm trying to get something working but a guide or some other documentation on how to write a quirk would be great. |
Z2M dont care of how the device is on the "hardware" side but you cant tracking the functions they is using for the device and see h´what they is doing and trying implementing it in the quirk. First you must making one signature and all imports for getting it working = ZHA is loading the quirk, best looking on this guide https://github.com/zigpy/zha-device-handlers#what-the-heck-is-a-quirk. Best is looking on one other devices that have similar function and and copy it to your quirk. You can also looking how deCONZ have made the implementation but tits working very different but some interesting then there discussion how implanting functions. If some more user is having the device and can coding or other user with knowledge its normally going well but can taking time getting all in place. The good thing is that IKEA is doing most thing pity standard Zigbee and i think its only the PPM and fan speed auto that is little tricky. I think its having function for filter time and then it shall being exchanged. I dont have the device and i cant doing coding so i cant helping so much but can making commands and sharing little knowledge. |
Hi, |
I have the same device. I can help with debugging adding missing information if someone points me to what have to be done. |
I've started with a quirk :)
Step 1: The quirk is recognised :) |
class PM25(_ConcentrationMixin, Cluster) is already in the zigpy code. https://github.com/zigpy/zigpy/pull/683/files#diff-ec4c5179b55bb221fdcf288218a3e4ee5ccabcbefa8497468b6231429f669174 But this type of sensor is not available in HA. I created a pull request to add the Particulate Matter 2.5 sensor to ZHA component. :) ============================= Here is a scan of all attribute values of the Starkvind: |
I have some things working :)
But i have one problem to solve.... I think we have three possible options. Make a custom cluster and let it be recognised as an onoff cluster. Or make a onoff cluster, always return a possive result, and push the command to the ikea manuf cluster. Or make an onoff cluster and intercept the command and change the cluster ID before sending the command. Whats the best option, if possible at all.... @MattWestb can you shine your light on this?
**Click to see the latest code!**"""Device handler for IKEA of Sweden STARKVIND Air purifier."""
import logging
from zigpy.profiles import zha
from zigpy.quirks import CustomCluster, CustomDevice
import zigpy.types as t
from zigpy.zcl.clusters.general import (
Basic,
Groups,
Identify,
Ota,
PollControl,
Scenes,
GreenPowerProxy,
Time,
OnOff,
AnalogInput,
)
from zigpy.zcl.clusters.hvac import Fan
from zhaquirks.ikea import IKEA
from zigpy.zcl.clusters.measurement import (
IlluminanceMeasurement,
PM25,
)
from zhaquirks import Bus
from zhaquirks.const import (
CLUSTER_ID,
DEVICE_TYPE,
ENDPOINT_ID,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
)
from typing import Any, List, Optional, Union
from zigpy.zcl import foundation
#IKEA_CLUSTER_ID = 0xFC7C # decimal = 64636
WWAH_CLUSTER_ID = 0xFC57 # decimal = 64599
IKEA_CLUSTER_UNKNOWN_ID = 0xFC7D
_LOGGER = logging.getLogger(__name__)
class manuSpecificIkeaAirPurifier(CustomCluster):
"""Ikea Manufacturer Specific AirPurifier."""
name: str = "Ikea Manufacturer Specific AirPurifier"
cluster_id: t.uint16_t = 0xFC7D #64637 0xFC7D control air purifier with manufacturer-specific attributes
ep_attribute: str = "ikea_manufacturer"
#attributes = TuyaMCUCluster.attributes.copy()
attributes = {
0x0000: ("filter_run_time", t.uint32_t, True),
0x0001: ("replace_filter", t.Bool, True),
0x0002: ("filter_life_time", t.uint32_t, True),
0x0003: ("disable_led", t.Bool, True),
0x0004: ("air_quality_25pm", t.uint16_t, True),
0x0005: ("child_lock", t.Bool, True),
0x0006: ("fan_target_mode", t.uint8_t, True),
0x0007: ("fan_current_mode", t.uint8_t, True),
0x0008: ("device_run_time", t.uint32_t, True),
}
async def configure_reporting(
self,
attribute,
min_interval,
max_interval,
reportable_change,
manufacturer=None,
):
"""Configure reporting."""
result = await super().configure_reporting_multiple(
{
0x0000: (600, 600, 0), # filter_run_time
#0x0001: (0, 3600, 1), # replace_filter
#0x0002: (600, 600, 1), # filter_life_time
#0x0003: (0, 3600, 1), # disable_led
0x0004: (60, 3600, 1), # Air Quality (PM2.5 in µg/m³)
#0x0005: (0, 3600, 1), # child_lock
0x0006: (0, 3600, 1), # Target Mode
0x0007: (0, 3600, 1), # Current Mode
#0x0008: (600, 600, 1), # device_run_time
},
manufacturer=0x117C)
return result
def __init__(self, *args, **kwargs):
"""Init."""
self._current_state = {}
super().__init__(*args, **kwargs)
self.endpoint.device.child_lock_bus.add_listener(self)
def _update_attribute(self, attrid, value):
super()._update_attribute(attrid, value)
if attrid == 0x0003:
self.endpoint.device.disable_led_bus.listener_event("update_state", value)
if attrid == 0x0004:
if value is not 0xFFFF: #pm 2.5 value if device is off
if value is not None and value >= 0:
self.endpoint.device.pm25_bus.listener_event("update_state", value)
if attrid == 0x0005:
self.endpoint.device.child_lock_bus.listener_event("update_state", value)
# def update_child_lock(self, attrid, value):
class childLockCluster(CustomCluster, OnOff):
""" xxxx """
cluster_id = OnOff.cluster_id
def __init__(self, *args, **kwargs):
"""Init."""
super().__init__(*args, **kwargs)
self.endpoint.device.child_lock_bus.add_listener(self)
# def _update_attribute(self, attrid, value):
def update_state(self, value):
"""childlock reported."""
self._update_attribute(0x0000, value)
#self.endpoint.device.child_lock_bus.listener_event("update_state", value)
def command(
self,
command_id: Union[foundation.GeneralCommand, int, t.uint8_t],
*args,
manufacturer: Optional[Union[int, t.uint16_t]] = None,
expect_reply: bool = True,
tsn: Optional[Union[int, t.uint8_t]] = None,
):
"""Command handler."""
src_ep = 1
dst_ep = self.endpoint.endpoint_id
device = self.endpoint.device
if tsn is None:
tsn = self._endpoint.device.application.get_sequence()
return device.request(
# device,
zha.PROFILE_ID,
0xFC7D, #OnOff.cluster_id,
src_ep,
dst_ep,
tsn,
bytes([src_ep, tsn, command_id]),
expect_reply=expect_reply,
)
# async def command(
# self,
# command_id: Union[foundation.GeneralCommand, int, t.uint8_t],
# *args,
# manufacturer: Optional[Union[int, t.uint16_t]] = None,
# expect_reply: bool = True,
# tsn: Optional[Union[int, t.uint8_t]] = None,
# ):
# """Override the default Cluster command."""
# """Command handler."""
# src_ep = 1
# dst_ep = self.endpoint.endpoint_id
# device = self.endpoint.device
# if tsn is None:
# tsn = self._endpoint.device.application.get_sequence()
# return device.request(
# # device,
# zha.PROFILE_ID,
# 0xFC7D, #OnOff.cluster_id,
# src_ep,
# dst_ep,
# tsn,
# bytes([src_ep, tsn, command_id]),
# expect_reply=expect_reply,
# )
class disableLedCluster(CustomCluster, OnOff):
""" disableLedCluster """
cluster_id = OnOff.cluster_id
def __init__(self, *args, **kwargs):
"""Init."""
super().__init__(*args, **kwargs)
self.endpoint.device.disable_led_bus.add_listener(self)
def update_state(self, value):
"""childlock reported."""
self._update_attribute(0x0000, value)
class PM25Cluster(CustomCluster, PM25):
"""Analog input cluster, only used to relay power consumption information to ElectricalMeasurementCluster."""
cluster_id = PM25.cluster_id
def __init__(self, *args, **kwargs):
"""Init."""
super().__init__(*args, **kwargs)
self.endpoint.device.pm25_bus.add_listener(self)
def update_state(self, value):
"""25pm reported."""
self._update_attribute(0x0000, value)
class IkeaSTARKVIND(CustomDevice):
"""STARKVIND Air purifier by IKEA of Sweden."""
def __init__(self, *args, **kwargs):
"""Init."""
self.pm25_bus = Bus()
self.child_lock_bus = Bus()
self.disable_led_bus = Bus()
super().__init__(*args, **kwargs)
signature = {
# <SimpleDescriptor endpoint=1 profile=260 device_type=7 (0x0007)
# device_version=0
# input_clusters=[0, 3, 4, 5, 514, 64599, 64637] output_clusters=[25, 1024, 1066]>
MODELS_INFO: [(IKEA, "STARKVIND Air purifier")],
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
INPUT_CLUSTERS: [
Basic.cluster_id, #0
Identify.cluster_id, #3
Groups.cluster_id, #4
Scenes.cluster_id, #5
Fan.cluster_id, #514 0x0202
WWAH_CLUSTER_ID, #64599 0xFC57
manuSpecificIkeaAirPurifier.cluster_id, #64637 0xFC7D
],
OUTPUT_CLUSTERS: [
Ota.cluster_id, #25 0x0019
IlluminanceMeasurement.cluster_id, #1024 0x0400
PM25.cluster_id, #1066 0x042A PM2.5 Measurement Cluster
],
},
# <SimpleDescriptor endpoint=242 profile=41440 device_type=97
# device_version=0
# input_clusters=[33] output_clusters=[33]>
242: {
PROFILE_ID: 0xA1E0, # 41440 (dec)
DEVICE_TYPE: 0x0061,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [
GreenPowerProxy.cluster_id, #0x0021 = GreenPowerProxy.cluster_id?
],
},
},
}
replacement = {
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
INPUT_CLUSTERS: [
Basic.cluster_id, #0
Identify.cluster_id, #3
Groups.cluster_id, #4
Scenes.cluster_id, #5
Fan.cluster_id, #514
WWAH_CLUSTER_ID, #64599 0xFC57
manuSpecificIkeaAirPurifier, #64637 0xFC7D control air purifier with manufacturer-specific attributes
PM25Cluster, #1066 0x042A PM2.5 Measurement Cluster
#PM25, #1066 0x042A PM2.5 Measurement Cluster
#childLockCluster,
],
OUTPUT_CLUSTERS: [
Ota.cluster_id, #25 0x0019
IlluminanceMeasurement.cluster_id, #1024 0x0400
],
},
2: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.COMBINED_INTERFACE,
INPUT_CLUSTERS: [
disableLedCluster,
],
OUTPUT_CLUSTERS: [ ],
},
# <SimpleDescriptor endpoint=242 profile=41440 device_type=97
# device_version=0
# input_clusters=[33] output_clusters=[33]>
242: {
PROFILE_ID: 0xA1E0, # 41440 (dec)
DEVICE_TYPE: 0x0061,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [
GreenPowerProxy.cluster_id, #0x0021 = GreenPowerProxy.cluster_id?
],
},
},
} |
Interestingly there seems to have intermediate values possible on the fan cluster even if the physical dial only increments by 5 steps : I wonder if something like this is possible : |
The fan cluster has only speed 1, 2 and 3 + ON + AUTO + Smart. An easier option is to add a select list to the "configuration" option. Is dat usable? |
Maybe someone can point me in the right direction...
Zigpy.zcl is sending a request, but nothing happens after that.
When i send the same write attribute command with ZHA toolkit i see the same "Sending request:". After that zigpy_deconz.zigbee.application is sending the request over zigbee.
How can we write attributes from a quirk? |
We is reading attribute for casting "tuya magic spell" and was also testing one version with writing one attribute but it was needed. Look in this class for see how its being done: zha-device-handlers/zhaquirks/tuya/ts004f.py Line 184 in 4956114
Ar you sure the attribute is one manufacture attribute then you have tagging it with IKEA as manufacture ? |
@MattWestb Thanks! I see we can write attribute from CustomDevice class. Tagging it with IKEA as manufacturer isn't necessary. Just tried different things to get it working :) |
Looks great !! Great work done !!! |
Hi! Is there anything special I have to do to get this functionality to show up? I paired the air purifier and only get an on/off function… |
@javontathomas Are you running the latest Home Assistant 2022.7.0 beta? |
Ahh, didn’t realize I had to be on beta. Thanks! Will switch over. |
Is there any chance you can add the IKEA Starkvind table? It is basically the same device, but built into a table.
|
I have a pull request ready to support this model :) |
@arnemauer Awesome, thanks! |
@kcleong This issue can be closed! 👍 |
Is your feature request related to a problem? Please describe.
N/A
Describe the solution you'd like
When adding the Starkvind in ZHA/home assistant only the fan is recognized. The device also support other sensors but those are not exposed. Would it be possible to also expose the other sensors which are supported by this device; pm25, air_quality, led_enable, child_lock, replace_filter.
Device signature - this can be acquired by removing the device from ZHA and pairing it again from the add devices screen. Be sure to add the entire content of the log panel after pairing the device to a code block below this line.
Additional context
Add any other context or screenshots about the feature request here.
The text was updated successfully, but these errors were encountered: