From 801b860c59f4f5a1dd0889ea97acaf18a5cdbd93 Mon Sep 17 00:00:00 2001 From: Vivien Boistuaud Date: Sat, 17 Jun 2023 15:29:03 +0200 Subject: [PATCH] [tradfri] Add support for Air Purifier (#14836) * [tradfri] Added Support for Air Purifier (#7) Added Support for Tradfri Air Purifier: * Added documentation disambiguation Tradfri vs Dirigera * Added Tradfri Air Purifier - fanMode and fanSpeed * Workable Tradfri Air Purifier basic implementation * Tradfri: modified fanMode type and definition * Tradfri Air Purifier: Added disableLed * Tradfri Air Purifier: Added lockPhysicalButton * Tradfri Air Purifier: Added airQuality data * Tradfri Air Purifier: Added filterCheck data * Tradfri Air Purifier: Added translations * Tradfri Air Purifier: Added filter_uptime * Tradfri Air Purifier: code optimization * Documentation for supported Air Purifier channels Fixes #14816 Signed-off-by: Vivien Boistuaud --- bundles/org.openhab.binding.tradfri/README.md | 96 ++++++--- .../internal/TradfriBindingConstants.java | 43 +++- .../internal/TradfriHandlerFactory.java | 3 + .../discovery/TradfriDiscoveryService.java | 3 + .../handler/TradfriAirPurifierHandler.java | 163 +++++++++++++++ .../model/TradfriAirPurifierData.java | 185 ++++++++++++++++++ .../resources/OH-INF/i18n/tradfri.properties | 30 +++ .../resources/OH-INF/thing/thing-types.xml | 121 ++++++++++++ .../TradfriDiscoveryServiceTest.java | 20 +- 9 files changed, 638 insertions(+), 26 deletions(-) create mode 100644 bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/handler/TradfriAirPurifierHandler.java create mode 100644 bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/model/TradfriAirPurifierData.java diff --git a/bundles/org.openhab.binding.tradfri/README.md b/bundles/org.openhab.binding.tradfri/README.md index 09066f1f92fbf..851b8eec704e1 100644 --- a/bundles/org.openhab.binding.tradfri/README.md +++ b/bundles/org.openhab.binding.tradfri/README.md @@ -1,6 +1,7 @@ # TRÅDFRI Binding This binding integrates the IKEA TRÅDFRI gateway and devices connected to it (such as dimmable LED bulbs). +This binding only supports IKEA TRÅDFRI gateway v1.x, it is **not** compatible with DIRIGERA. ## Supported Things @@ -22,22 +23,34 @@ These are: | Non-Colour Controller | 0x0820 | 0820 | | Non-Colour Scene Controller | 0x0830 | 0830 | | Control Outlet | 0x0010 | 0010 | -| Window Covering Device | 0x0202 | 0202 | -| Window Covering Controller | 0x0202 | 0203 | The following matrix lists the capabilities (channels) for each of the supported lighting device types: -| Thing type | Brightness | Color | Color Temperature | Battery Level | Battery Low | Power | Position | -|-------------|:----------:|:-----:|:-----------------:|:-------------:|:-----------:|:-----:|:---------| -| 0010 | | | | | | X | | -| 0100 | X | | | | | | | -| 0220 | X | | X | | | | | -| 0210 | | X | X | | | | | -| 0107 | | | | X | X | | | -| 0820 | | | | X | X | | | -| 0830 | | | | X | X | | | -| 0202 | | | | X | X | | X | -| 0203 | | | | X | X | | | +| Thing type | Brightness | Color | Color Temperature | Battery Level | Battery Low | Power | +|-------------|:----------:|:-----:|:-----------------:|:-------------:|:-----------:|:-----:| +| 0010 | | | | | | X | +| 0100 | X | | | | | | +| 0220 | X | | X | | | | +| 0210 | | X | X | | | | +| 0107 | | | | X | X | | +| 0820 | | | | X | X | | +| 0830 | | | | X | X | | + +The following things are also supported even thought they are not standardized in Zigbee Light Link: + +| Device type | Zigbee Device ID | Thing type | +|---------------------------------|------------------|------------| +| Window Covering Device | 0x0202 | 0202 | +| Window Covering Controller | 0x0203 | 0203 | +| Air Purifier | 0x0007 | 0007 | + +The following matrix lists the capabilities (channels) for each of the supported non-lighting device types: + +| Thing type | Battery Level | Battery Low | Position | Fan Mode | Lock Button | Disabled LED | Air Quality | Fan Speed | Filter Check | Filter Uptime | +|-------------|:-------------:|:-----------:|:--------:|:--------:|:-----------:|:------------:|:-----------:|:---------:|:------------:|:-------------:| +| 0202 | X | X | X | | | | | | | | +| 0203 | X | X | | | | | | | | | +| 0007 | | | | X | X | X | X | X | X | X | ## Thing Configuration @@ -65,17 +78,33 @@ The control outlet supports the `power` channel. A blind or curtain supports, beside `battery_level` and `battery_low` channels, a `positon` channel. -Refer to the matrix above. - -| Channel Type ID | Item Type | Description | -|-------------------|---------------|--------------------------------------------------------| -| brightness | Dimmer | The brightness of the bulb in percent | -| color_temperature | Dimmer | color temperature from 0% = cold to 100% = warm | -| color | Color | full color | -| battery_level | Number | battery level (in %) | -| battery_low | Switch | battery low warning (<=10% = ON, >10% = OFF) | -| power | Switch | power switch | -| position | Rollershutter | position of the blinds from 0% = open to 100% = closed | +An air purifier supports: +* `fan_mode` and `fan_speed` channels, which allows for control of the fan and reading of the current speed. +* `disable_led` and `lock_button` channels, to respectively disable the LED's and lock the button on the physical device. +* `air_quality_pm25` and `air_quality_rating` channels, which reads the particulate matter 2.5μm and corresponding indication of air quality (similar to Tradfri app rating). +* `filter_check_next` and `filter_check_alarm` channels, which represents the remaining number of minutes until the next filter check and whether it is time to do the filter check now. Filter check must be completed through the TRÅDFRI app (or on the hardware buttons in case of replacement). +* a `filter_uptime` channel, which represents the current time since last filter change. + +Refer to the matrixes above. + +| Channel Type ID | Item Type | Description | +|---------------------|----------------------|----------------------------------------------------------------------------------------------| +| brightness | Dimmer | The brightness of the bulb in percent | +| color_temperature | Dimmer | Color temperature from 0% = cold to 100% = warm | +| color | Color | Full color | +| battery_level | Number | Battery level (in %) | +| battery_low | Switch | Battery low warning (<=10% = ON, >10% = OFF) | +| power | Switch | Power switch | +| position | Rollershutter | Position of the blinds from 0% = open to 100% = closed | +| fan_mode | Number | Fan mode, target speed of the fan (0 = off, 1 = auto, 10..50 = Level 1 to 5) | +| fan_speed | Number | Current Fan Speed between 0 (off) and 50 (maximum speed) | +| disable_led | Switch | Disables the LED's on the device | +| lock_button | Switch | Disables the physical button on the device (applications can still make changes) | +| air_quality_pm25 | Number:Dimensionless | Density of Particulate Matter of 2.5μm, measured in ppm | +| air_quality_rating | Number | Gives a rating about air quality (1 = Good, 2 = OK, 3 = Bad) similar to Tradfri app | +| filter_check_next | Number:Time | Time in minutes before the next filter check if > 0, if < 0 you are late checking the filter | +| filter_check_alarm | Switch | When ON, you must perform a filter check (i.e. `filter_check_next` is < 0) | +| filter_uptime | Number:Time | Time elapsed since the last filter change, in minutes | ## Full Example @@ -89,6 +118,7 @@ Bridge tradfri:gateway:mygateway [ host="192.168.0.177", code="EHPW5rIJKyXFgjH3" 0830 myRemoteControl "My Remote Control" [ id=65545 ] 0010 myControlOutlet "My Control Outlet" [ id=65542 ] 0202 myBlinds "My Blinds" [ id=65547 ] + 0007 myAirPurifier "My Air Purifier" [ id=65548 ] } ``` @@ -103,6 +133,15 @@ Number RemoteControlBatteryLevel { channel="tradfri:0830:mygateway:myRemoteContr Switch RemoteControlBatteryLow { channel="tradfri:0830:mygateway:myRemoteControl:battery_low" } Switch ControlOutlet { channel="tradfri:0010:mygateway:myControlOutlet:power" } Rollershutter BlindPosition { channel="tradfri:0202:mygateway:myBlinds:position" } +Number AirPurifierFanMode { channel="tradfri:0007:mygateway:myAirPurifier:fan_mode" } +Number AirPurifierFanSpeed { channel="tradfri:0007:mygateway:myAirPurifier:fan_speed" } +Switch AirPurifierDisableLED { channel="tradfri:0007:mygateway:myAirPurifier:disable_led" } +Switch AirPurifierLockPhysicalButton { channel="tradfri:0007:mygateway:myAirPurifier:lock_button" } +Number AirPurifierQualityPM25 { channel="tradfri:0007:mygateway:myAirPurifier:air_quality_pm25" } +Number AirPurifierQualityRating { channel="tradfri:0007:mygateway:myAirPurifier:air_quality_rating" } +Number AirPurifierFilterCheckTTL { channel="tradfri:0007:mygateway:myAirPurifier:filter_check_next" } +Switch AirPurifierFilterCheckAlarm { channel="tradfri:0007:mygateway:myAirPurifier:filter_check_alarm" } +Number AirPurifierFilterUptime { channel="tradfri:0007:mygateway:myAirPurifier:filter_uptime" } ``` demo.sitemap: @@ -119,6 +158,15 @@ sitemap demo label="Main Menu" Switch item=RemoteControlBatteryLow label="Battery Low Warning" Switch item=ControlOutlet label="Power Switch" Switch item=BlindPosition label="Blind Position [%d]" + Selection item=AirPurifierFanMode label="Fan Mode" + Text item=AirPurifierFanSpeed label="Current Fan Speed [%d]" + Switch item=AirPurifierDisableLED label="Disable LEDs" + Switch item=AirPurifierLockPhysicalButton label="Disable Physical Buttons" + Text item=AirPurifierQualityPM25 label="PM2.5" + Text item=AirPurifierQualityRating label="Air Quality" + Text item=AirPurifierFilterCheckTTL label="TTL before next filter check [%d min]" + Text item=AirPurifierFilterCheckAlarm label="Need to Check Filter [%s]" + Text item=AirPurifierFilterUptime label="Current filter uptime [%d min]" } } ``` diff --git a/bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/TradfriBindingConstants.java b/bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/TradfriBindingConstants.java index 45b566409751c..da11d714fa259 100644 --- a/bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/TradfriBindingConstants.java +++ b/bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/TradfriBindingConstants.java @@ -27,6 +27,7 @@ * @author Kai Kreuzer - Initial contribution * @author Christoph Weitkamp - Added support for remote controller and motion sensor devices (read-only battery level) * @author Manuel Raffel - Added support for blinds + * @author Vivien Boistuaud - Added support for air purifier */ @NonNullByDefault public class TradfriBindingConstants { @@ -45,6 +46,7 @@ public class TradfriBindingConstants { public static final ThingTypeUID THING_TYPE_MOTION_SENSOR = new ThingTypeUID(BINDING_ID, "0107"); public static final ThingTypeUID THING_TYPE_BLINDS = new ThingTypeUID(BINDING_ID, "0202"); public static final ThingTypeUID THING_TYPE_OPEN_CLOSE_REMOTE_CONTROL = new ThingTypeUID(BINDING_ID, "0203"); + public static final ThingTypeUID THING_TYPE_AIR_PURIFIER = new ThingTypeUID(BINDING_ID, "0007"); public static final Set SUPPORTED_LIGHT_TYPES_UIDS = Collections .unmodifiableSet(Stream.of(THING_TYPE_DIMMABLE_LIGHT, THING_TYPE_COLOR_TEMP_LIGHT, THING_TYPE_COLOR_LIGHT) @@ -54,6 +56,8 @@ public class TradfriBindingConstants { public static final Set SUPPORTED_BLINDS_TYPES_UIDS = Collections.singleton(THING_TYPE_BLINDS); + public static final Set SUPPORTED_AIR_PURIFIER_TYPES_UIDS = Set.of(THING_TYPE_AIR_PURIFIER); + // List of all Gateway Configuration Properties public static final String GATEWAY_CONFIG_HOST = "host"; public static final String GATEWAY_CONFIG_PORT = "port"; @@ -70,7 +74,8 @@ public class TradfriBindingConstants { public static final Set SUPPORTED_DEVICE_TYPES_UIDS = Collections.unmodifiableSet(Stream .of(SUPPORTED_LIGHT_TYPES_UIDS.stream(), SUPPORTED_CONTROLLER_TYPES_UIDS.stream(), - SUPPORTED_PLUG_TYPES_UIDS.stream(), SUPPORTED_BLINDS_TYPES_UIDS.stream()) + SUPPORTED_PLUG_TYPES_UIDS.stream(), SUPPORTED_BLINDS_TYPES_UIDS.stream(), + SUPPORTED_AIR_PURIFIER_TYPES_UIDS.stream()) .reduce(Stream::concat).orElseGet(Stream::empty).collect(Collectors.toSet())); public static final Set SUPPORTED_THING_TYPES_UIDS = Collections @@ -85,9 +90,20 @@ public class TradfriBindingConstants { public static final String CHANNEL_POSITION = "position"; public static final String CHANNEL_BATTERY_LEVEL = "battery_level"; public static final String CHANNEL_BATTERY_LOW = "battery_low"; + public static final String CHANNEL_FAN_MODE = "fan_mode"; + public static final String CHANNEL_FAN_SPEED = "fan_speed"; + public static final String CHANNEL_DISABLE_LED = "disable_led"; + public static final String CHANNEL_LOCK_BUTTON = "lock_button"; + public static final String CHANNEL_AIR_QUALITY_PM25 = "air_quality_pm25"; + public static final String CHANNEL_AIR_QUALITY_RATING = "air_quality_rating"; + public static final String CHANNEL_FILTER_CHECK_NEXT = "filter_check_next"; + public static final String CHANNEL_FILTER_CHECK_ALARM = "filter_check_alarm"; + public static final String CHANNEL_FILTER_UPTIME = "filter_uptime"; // IPSO Objects public static final String DEVICES = "15001"; + public static final String AIR_PURIFIER = "15025"; + public static final String AIR_QUALITY = "5907"; public static final String AUTH_PATH = "9063"; public static final String BLINDS = "15015"; public static final String CLIENT_IDENTITY_PROPOSED = "9090"; @@ -107,6 +123,9 @@ public class TradfriBindingConstants { public static final String END_TIME_HR = "9048"; public static final String END_TIME_MN = "9049"; public static final String ERROR_TAG = "errorcode"; + public static final String FAN_MODE = "5900"; + public static final String FAN_SPEED = "5908"; + public static final String FILTER_UPTIME = "5902"; public static final String FORCE_CHECK_OTA_UPDATE = "9032"; public static final String GATEWAY = "15011"; public static final String GATEWAY_DETAILS = "15012"; @@ -125,9 +144,11 @@ public class TradfriBindingConstants { public static final String IKEA_MOODS = "9068"; public static final String INSTANCE_ID = "9003"; public static final String LAST_SEEN = "9020"; + public static final String LED_DISABLE = "5906"; public static final String LIGHT = "3311"; public static final int LIGHTS_OFF_SMART_TASK = 2; public static final String LIGHT_SETTING = "15013"; + public static final String LOCK_PHYSICAL_BUTTON = "5905"; public static final int LOSS_OF_INTERNET_CONNECTIVITY = 5001; public static final String MASTER_TOKEN_TAG = "9036"; public static final String MAX_MSR_VALUE = "5602"; @@ -137,6 +158,7 @@ public class TradfriBindingConstants { public static final String NAME = "9001"; public static final int NEW_FIRMWARE_AVAILABLE = 1001; public static final String NEW_PSK_BY_GW = "9091"; + public static final String NEXT_FILTER_CHECK = "5910"; public static final String NOTIFICATION_EVENT = "9015"; public static final String NOTIFICATION_NVPAIR = "9017"; public static final String NOTIFICATION_STATE = "9014"; @@ -201,8 +223,27 @@ public class TradfriBindingConstants { public static final String TYPE_SENSOR = "4"; public static final String TYPE_REPEATER = "6"; public static final String TYPE_BLINDS = "7"; + public static final String TYPE_AIR_PURIFIER = "10"; + public static final String DEVICE_VENDOR = "0"; public static final String DEVICE_MODEL = "1"; public static final String DEVICE_FIRMWARE = "3"; public static final String DEVICE_BATTERY_LEVEL = "9"; + + // List of Air Purifier Constants + public static final int FAN_MODE_OFF = 0; + public static final int FAN_MODE_AUTO = 1; + public static final int FAN_MODE_SPEED1 = 10; + public static final int FAN_MODE_SPEED2 = 20; + public static final int FAN_MODE_SPEED3 = 30; + public static final int FAN_MODE_SPEED4 = 40; + public static final int FAN_MODE_SPEED5 = 50; + + public static final Set AIR_PURIFIER_FANMODE = Set.of(FAN_MODE_OFF, FAN_MODE_AUTO, FAN_MODE_SPEED1, + FAN_MODE_SPEED2, FAN_MODE_SPEED3, FAN_MODE_SPEED4, FAN_MODE_SPEED5); + + public static final int AIR_PURIFIER_AIR_QUALITY_OK = 36; + public static final int AIR_PURIFIER_AIR_QUALITY_BAD = 86; + + public static final int AIR_PURIFIER_AIR_QUALITY_UNDEFINED = 65535; } diff --git a/bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/TradfriHandlerFactory.java b/bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/TradfriHandlerFactory.java index 5876bf4079946..38886862be3b5 100644 --- a/bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/TradfriHandlerFactory.java +++ b/bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/TradfriHandlerFactory.java @@ -16,6 +16,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.tradfri.internal.handler.TradfriAirPurifierHandler; import org.openhab.binding.tradfri.internal.handler.TradfriBlindHandler; import org.openhab.binding.tradfri.internal.handler.TradfriControllerHandler; import org.openhab.binding.tradfri.internal.handler.TradfriGatewayHandler; @@ -63,6 +64,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { return new TradfriLightHandler(thing); } else if (SUPPORTED_PLUG_TYPES_UIDS.contains(thingTypeUID)) { return new TradfriPlugHandler(thing); + } else if (THING_TYPE_AIR_PURIFIER.equals(thingTypeUID)) { + return new TradfriAirPurifierHandler(thing); } return null; } diff --git a/bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/discovery/TradfriDiscoveryService.java b/bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/discovery/TradfriDiscoveryService.java index ede2e00f36935..5fd3fbcb6717c 100644 --- a/bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/discovery/TradfriDiscoveryService.java +++ b/bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/discovery/TradfriDiscoveryService.java @@ -49,6 +49,7 @@ * @author Christoph Weitkamp - Added support for remote controller and motion sensor devices (read-only battery level) * @author Andre Fuechsel - fixed the results removal * @author Manuel Raffel - Added support for blinds + * @author Vivien Boistuaud - Added support for Air Purifiers */ @NonNullByDefault public class TradfriDiscoveryService extends AbstractDiscoveryService @@ -158,6 +159,8 @@ public void onUpdate(@Nullable String instanceId, @Nullable JsonObject data) { } else if (TYPE_SENSOR.equals(type) && data.has(SENSOR)) { // Motion sensor thingId = new ThingUID(THING_TYPE_MOTION_SENSOR, bridge, Integer.toString(id)); + } else if (TYPE_AIR_PURIFIER.equals(type) && data.has(AIR_PURIFIER)) { + thingId = new ThingUID(THING_TYPE_AIR_PURIFIER, bridge, Integer.toString(id)); } if (thingId == null) { diff --git a/bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/handler/TradfriAirPurifierHandler.java b/bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/handler/TradfriAirPurifierHandler.java new file mode 100644 index 0000000000000..149d09bf36112 --- /dev/null +++ b/bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/handler/TradfriAirPurifierHandler.java @@ -0,0 +1,163 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.tradfri.internal.handler; + +import static org.openhab.binding.tradfri.internal.TradfriBindingConstants.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.tradfri.internal.TradfriCoapClient; +import org.openhab.binding.tradfri.internal.model.TradfriAirPurifierData; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonElement; + +/** + * The {@link TradfriAirPurifierHandler} is responsible for handling commands and status updates + * for Starkvind Air Purifiers. + * + * @author Vivien Boistuaud - Initial contribution + */ +@NonNullByDefault +public class TradfriAirPurifierHandler extends TradfriThingHandler { + + private final Logger logger = LoggerFactory.getLogger(TradfriAirPurifierHandler.class); + + public TradfriAirPurifierHandler(Thing thing) { + super(thing); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (active) { + if (command instanceof RefreshType) { + TradfriCoapClient coapClient = this.coapClient; + if (coapClient != null) { + logger.debug("Refreshing channel {}", channelUID); + coapClient.asyncGet(this); + } else { + logger.debug("coapClient is null!"); + } + return; + } + + switch (channelUID.getId()) { + case CHANNEL_FAN_MODE: + handleFanModeCommand(command); + break; + case CHANNEL_DISABLE_LED: + handleDisableLed(command); + break; + case CHANNEL_LOCK_BUTTON: + handleLockButton(command); + break; + default: + logger.error("Unknown channel UID {}", channelUID); + } + } + } + + private void handleFanModeCommand(Command command) { + if (command instanceof Number) { + set(new TradfriAirPurifierData().setFanMode((Number) command).getJsonString()); + } else { + logger.debug("Cannot handle command '{}' of type {} for channel '{}'", command, command.getClass(), + CHANNEL_FAN_MODE); + } + } + + private void handleDisableLed(Command command) { + if (command instanceof OnOffType) { + set(new TradfriAirPurifierData().setDisableLed((OnOffType) command).getJsonString()); + } else { + logger.debug("Cannot handle command '{}' of type {} for channel '{}'", command, command.getClass(), + CHANNEL_DISABLE_LED); + } + } + + private void handleLockButton(Command command) { + if (command instanceof OnOffType) { + set(new TradfriAirPurifierData().setLockPhysicalButton((OnOffType) command).getJsonString()); + } else { + logger.debug("Cannot handle command '{}' of type {} for channel '{}'", command, command.getClass(), + CHANNEL_DISABLE_LED); + } + } + + @Override + public void onUpdate(JsonElement data) { + if (active && !(data.isJsonNull())) { + TradfriAirPurifierData state = new TradfriAirPurifierData(data); + updateStatus(state.getReachabilityStatus() ? ThingStatus.ONLINE : ThingStatus.OFFLINE); + + State fanMode = state.getFanMode(); + if (fanMode != null) { + updateState(CHANNEL_FAN_MODE, fanMode); + } + + State fanSpeed = state.getFanSpeed(); + if (fanSpeed != null) { + updateState(CHANNEL_FAN_SPEED, fanSpeed); + } + + State disableLed = state.getDisableLed(); + if (disableLed != null) { + updateState(CHANNEL_DISABLE_LED, disableLed); + } + + State lockPhysicalButton = state.getLockPhysicalButton(); + if (lockPhysicalButton != null) { + updateState(CHANNEL_LOCK_BUTTON, lockPhysicalButton); + } + + State airQualityPm25 = state.getAirQualityPM25(); + if (airQualityPm25 != null) { + updateState(CHANNEL_AIR_QUALITY_PM25, airQualityPm25); + } + + State airQualityRating = state.getAirQualityRating(); + if (airQualityRating != null) { + updateState(CHANNEL_AIR_QUALITY_RATING, airQualityRating); + } + + State nextFilterCheckTTL = state.getNextFilterCheckTTL(); + if (nextFilterCheckTTL != null) { + updateState(CHANNEL_FILTER_CHECK_NEXT, nextFilterCheckTTL); + } + + State filterCheckAlarm = state.getFilterCheckAlarm(); + if (filterCheckAlarm != null) { + updateState(CHANNEL_FILTER_CHECK_ALARM, filterCheckAlarm); + } + + State filterUptime = state.getFilterUptime(); + if (filterUptime != null) { + updateState(CHANNEL_FILTER_UPTIME, filterUptime); + } + + logger.debug( + "Updating thing for airPurifierId {} to state {fanMode: {}, fanSpeed: {}, disableLed: {}, lockButton: {}, airQualityPm25: {}, airQualityRating: {}, nextFilterCheckTTL: {}, filterCheckAlarm: {}, filterUptime: {}, firmwareVersion: {}, modelId: {}, vendor: {}}", + state.getDeviceId(), state.getFanMode(), state.getFanSpeed(), state.getDisableLed(), + state.getLockPhysicalButton(), state.getAirQualityPM25(), state.getAirQualityRating(), + state.getNextFilterCheckTTL(), state.getFilterCheckAlarm(), state.getFilterUptime(), + state.getFirmwareVersion(), state.getModelId(), state.getVendor()); + } + } +} diff --git a/bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/model/TradfriAirPurifierData.java b/bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/model/TradfriAirPurifierData.java new file mode 100644 index 0000000000000..8d67f4a96237d --- /dev/null +++ b/bundles/org.openhab.binding.tradfri/src/main/java/org/openhab/binding/tradfri/internal/model/TradfriAirPurifierData.java @@ -0,0 +1,185 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.tradfri.internal.model; + +import static org.openhab.binding.tradfri.internal.TradfriBindingConstants.*; + +import javax.measure.quantity.Dimensionless; +import javax.measure.quantity.Time; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; + +/** + * The {@link TradfriAirPurifierData} class is a Java wrapper for the raw JSON data about the air purifier state. + * + * @author Vivien Boistuaud - Initial contribution + */ +@NonNullByDefault +public class TradfriAirPurifierData extends TradfriDeviceData { + + private final Logger logger = LoggerFactory.getLogger(TradfriAirPurifierData.class); + + public TradfriAirPurifierData() { + super(AIR_PURIFIER); + } + + public TradfriAirPurifierData(JsonElement json) { + super(AIR_PURIFIER, json); + } + + public String getJsonString() { + return root.toString(); + } + + public @Nullable DecimalType getFanMode() { + JsonElement fanMode = attributes.get(FAN_MODE); + if (fanMode != null) { + int modeValue = fanMode.getAsInt(); + if (AIR_PURIFIER_FANMODE.contains(modeValue)) { + return new DecimalType(modeValue); + } else { + logger.debug("Invalid speedMode is '{}': unknown value", modeValue); + return null; + } + } else { + return null; + } + } + + public TradfriAirPurifierData setFanMode(Number speedValue) { + int speed = speedValue.intValue(); + if (AIR_PURIFIER_FANMODE.contains(speed)) { + attributes.add(FAN_MODE, new JsonPrimitive(speed)); + } else { + logger.debug("Could not set fanMode to '{}': unknown value", speed); + } + return this; + } + + public @Nullable DecimalType getFanSpeed() { + JsonElement fanSpeed = attributes.get(FAN_SPEED); + if (fanSpeed != null) { + int speedValue = fanSpeed.getAsInt(); + return new DecimalType(speedValue); + } else { + return null; + } + } + + public @Nullable OnOffType getDisableLed() { + JsonElement ledOnOff = attributes.get(LED_DISABLE); + if (ledOnOff != null) { + boolean ledStatus = ledOnOff.getAsInt() != 0; + return OnOffType.from(ledStatus); + } else { + return null; + } + } + + public TradfriAirPurifierData setDisableLed(OnOffType disableOnOff) { + attributes.add(LED_DISABLE, new JsonPrimitive(OnOffType.ON.equals(disableOnOff) ? 1 : 0)); + return this; + } + + public @Nullable OnOffType getLockPhysicalButton() { + JsonElement lockPhysicalButton = attributes.get(LOCK_PHYSICAL_BUTTON); + if (lockPhysicalButton != null) { + boolean isLocked = lockPhysicalButton.getAsInt() != 0; + return OnOffType.from(isLocked); + } else { + return null; + } + } + + public TradfriAirPurifierData setLockPhysicalButton(OnOffType lockPhysicalButton) { + attributes.add(LOCK_PHYSICAL_BUTTON, new JsonPrimitive(OnOffType.ON.equals(lockPhysicalButton) ? 1 : 0)); + return this; + } + + public @Nullable State getAirQualityPM25() { + JsonElement airQuality = attributes.get(AIR_QUALITY); + if (airQuality != null) { + int pm25InPpm = airQuality.getAsInt(); + if (pm25InPpm != AIR_PURIFIER_AIR_QUALITY_UNDEFINED) { + return new QuantityType(pm25InPpm, Units.PARTS_PER_MILLION); + } else { + return UnDefType.UNDEF; + } + } else { + return null; + } + } + + public @Nullable State getAirQualityRating() { + State pm25State = getAirQualityPM25(); + if (pm25State != null) { + if (pm25State instanceof Number) { + int pm25Value = ((Number) pm25State).intValue(); + int qualityRating = 1; + + if (pm25Value >= AIR_PURIFIER_AIR_QUALITY_BAD) { + qualityRating = 3; + } else if (pm25Value >= AIR_PURIFIER_AIR_QUALITY_OK) { + qualityRating = 2; + } + + return new DecimalType(qualityRating); + } + return UnDefType.UNDEF; + } else { + return null; + } + } + + public @Nullable QuantityType