[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 <github@boistuaud.net>
This commit is contained in:
Vivien Boistuaud
2023-06-17 15:29:03 +02:00
committed by GitHub
parent b505f7b12c
commit 801b860c59
9 changed files with 637 additions and 25 deletions

View File

@@ -27,6 +27,7 @@ import org.openhab.core.thing.ThingTypeUID;
* @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<ThingTypeUID> 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<ThingTypeUID> SUPPORTED_BLINDS_TYPES_UIDS = Collections.singleton(THING_TYPE_BLINDS);
public static final Set<ThingTypeUID> 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<ThingTypeUID> 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<ThingTypeUID> 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<Integer> 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;
}

View File

@@ -16,6 +16,7 @@ import static org.openhab.binding.tradfri.internal.TradfriBindingConstants.*;
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 class TradfriHandlerFactory extends BaseThingHandlerFactory {
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;
}

View File

@@ -49,6 +49,7 @@ import com.google.gson.JsonSyntaxException;
* @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 class TradfriDiscoveryService extends AbstractDiscoveryService
} 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) {

View File

@@ -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());
}
}
}

View File

@@ -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<Dimensionless>(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<Time> getNextFilterCheckTTL() {
JsonElement nextFilterCheckTTL = attributes.get(NEXT_FILTER_CHECK);
if (nextFilterCheckTTL != null) {
int remainingMinutes = nextFilterCheckTTL.getAsInt();
return new QuantityType<Time>(remainingMinutes, Units.MINUTE);
} else {
return null;
}
}
public @Nullable OnOffType getFilterCheckAlarm() {
QuantityType<Time> ttl = getNextFilterCheckTTL();
if (ttl != null) {
int ttlValue = ttl.intValue();
return ttlValue < 0 ? OnOffType.ON : OnOffType.OFF;
} else {
return null;
}
}
public @Nullable QuantityType<Time> getFilterUptime() {
JsonElement filterUptime = attributes.get(FILTER_UPTIME);
if (filterUptime != null) {
int filterUptimeMinutes = filterUptime.getAsInt();
return new QuantityType<Time>(filterUptimeMinutes, Units.MINUTE);
} else {
return null;
}
}
}

View File

@@ -5,6 +5,8 @@ addon.tradfri.description = This binding supports IKEA TRÅDFRI lighting devices
# thing types
thing-type.tradfri.0007.label = Air Purifier
thing-type.tradfri.0007.description = This represents the air purifier sensors and controls.
thing-type.tradfri.0010.label = On/Off Plug
thing-type.tradfri.0010.description = A plug that can be switched on and off.
thing-type.tradfri.0100.label = Dimmable Light
@@ -43,6 +45,34 @@ thing-type.config.tradfri.device.id.description = The identifier of the device o
# channel types
channel-type.tradfri.air-quality-pm25.label = PM 2.5
channel-type.tradfri.air-quality-pm25.description = Density of Particulate Matter of 2.5μm measured by the Air Purifier, in ppm.
channel-type.tradfri.air-quality-rating.label = Air Quality
channel-type.tradfri.air-quality-rating.description = An evaluation of the Air Quality between 1 (Good) and 3 (Bad).
channel-type.tradfri.air-quality-rating.state.option.1 = Good
channel-type.tradfri.air-quality-rating.state.option.2 = OK
channel-type.tradfri.air-quality-rating.state.option.3 = Bad
channel-type.tradfri.disable-led.label = Disable LED
channel-type.tradfri.disable-led.description = Disables the LED's on the Air Purifier.
channel-type.tradfri.fan-mode.label = Fan Speed Mode
channel-type.tradfri.fan-mode.description = Controls the configured fan speed.
channel-type.tradfri.fan-mode.state.option.0 = Power Off
channel-type.tradfri.fan-mode.state.option.1 = Automatic
channel-type.tradfri.fan-mode.state.option.10 = Speed 1
channel-type.tradfri.fan-mode.state.option.20 = Speed 2
channel-type.tradfri.fan-mode.state.option.30 = Speed 3
channel-type.tradfri.fan-mode.state.option.40 = Speed 4
channel-type.tradfri.fan-mode.state.option.50 = Speed 5
channel-type.tradfri.fan-speed.label = Current Fan Speed
channel-type.tradfri.fan-speed.description = Displays the current fan speed (0..50).
channel-type.tradfri.filter-check-alarm.label = Filter Check Alarm
channel-type.tradfri.filter-check-alarm.description = When this value is ON, you need to perform a filter check.
channel-type.tradfri.filter-check-next.label = Next Filter Check
channel-type.tradfri.filter-check-next.description = The number of minutes before the next filter check (or time since check is required if below 0).
channel-type.tradfri.filter-uptime.label = Filter Uptime
channel-type.tradfri.filter-uptime.description = The duration for which the current filter was used.
channel-type.tradfri.lock-button.label = Lock Physical Button
channel-type.tradfri.lock-button.description = When ON, locks the physical button on the device.
channel-type.tradfri.position.label = Position
channel-type.tradfri.position.description = Control the position of the blind or curtain in percent from 0 (open) to 100 (closed).

View File

@@ -176,6 +176,31 @@
<config-description-ref uri="thing-type:tradfri:device"/>
</thing-type>
<thing-type id="0007">
<supported-bridge-type-refs>
<bridge-type-ref id="gateway"/>
</supported-bridge-type-refs>
<label>Air Purifier</label>
<description>This represents the air purifier sensors and controls.</description>
<channels>
<channel id="fan_speed" typeId="fan-speed"/>
<channel id="fan_mode" typeId="fan-mode"/>
<channel id="disable_led" typeId="disable-led"/>
<channel id="lock_button" typeId="lock-button"/>
<channel id="air_quality_pm25" typeId="air-quality-pm25"/>
<channel id="air_quality_rating" typeId="air-quality-rating"/>
<channel id="filter_check_next" typeId="filter-check-next"/>
<channel id="filter_check_alarm" typeId="filter-check-alarm"/>
<channel id="filter_uptime" typeId="filter-uptime"/>
</channels>
<representation-property>id</representation-property>
<config-description-ref uri="thing-type:tradfri:device"/>
</thing-type>
<channel-type id="position">
<item-type>Rollershutter</item-type>
<label>Position</label>
@@ -183,4 +208,100 @@
<category>Blinds</category>
</channel-type>
<channel-type id="fan-mode">
<item-type>Number:Dimensionless</item-type>
<label>Fan Speed Mode</label>
<description>Controls the configured fan speed.</description>
<state pattern="%d">
<options>
<option value="0">Power Off</option>
<option value="1">Automatic</option>
<option value="10">Speed 1</option>
<option value="20">Speed 2</option>
<option value="30">Speed 3</option>
<option value="40">Speed 4</option>
<option value="50">Speed 5</option>
</options>
</state>
</channel-type>
<channel-type id="fan-speed">
<item-type>Number:Dimensionless</item-type>
<label>Current Fan Speed</label>
<description>Displays the current fan speed (0..50).</description>
<category>Fan</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="disable-led">
<item-type>Switch</item-type>
<label>Disable LED</label>
<description>Disables the LED's on the Air Purifier.</description>
</channel-type>
<channel-type id="lock-button">
<item-type>Switch</item-type>
<label>Lock Physical Button</label>
<description>When ON, locks the physical button on the device.</description>
</channel-type>
<channel-type id="air-quality-pm25">
<item-type>Number:Dimensionless</item-type>
<label>PM 2.5</label>
<description>Density of Particulate Matter of 2.5μm measured by the Air Purifier, in ppm.</description>
<state readOnly="true" pattern="%d ppm"/>
</channel-type>
<channel-type id="air-quality-rating">
<item-type>Number:Dimensionless</item-type>
<label>Air Quality</label>
<description>An evaluation of the Air Quality between 1 (Good) and 3 (Bad).</description>
<state readOnly="true" pattern="%d">
<options>
<option value="1">Good</option>
<option value="2">OK</option>
<option value="3">Bad</option>
</options>
</state>
</channel-type>
<channel-type id="filter-check-next">
<item-type>Number:Time</item-type>
<label>Next Filter Check</label>
<description>The number of minutes before the next filter check (or time since check is required if below 0).</description>
<category>Time</category>
<state readOnly="true" pattern="%.2f d"/>
</channel-type>
<channel-type id="filter-check-alarm">
<item-type>Switch</item-type>
<label>Filter Check Alarm</label>
<description>When this value is ON, you need to perform a filter check.</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="filter-uptime">
<item-type>Number:Time</item-type>
<label>Filter Uptime</label>
<description>The duration for which the current filter was used.</description>
<category>Time</category>
<state readOnly="true" pattern="%.2f week"/>
</channel-type>
</thing:thing-descriptions>

View File

@@ -46,6 +46,7 @@ import com.google.gson.JsonParser;
*
* @author Kai Kreuzer - Initial contribution
* @author Christoph Weitkamp - Added support for remote controller and motion sensor devices (read-only battery level)
* @author Vivien Boistuaud - Added unit test for Air Purifier
*/
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@@ -92,7 +93,7 @@ public class TradfriDiscoveryServiceTest {
@Test
public void correctSupportedTypes() {
assertThat(discovery.getSupportedThingTypes().size(), is(9));
assertThat(discovery.getSupportedThingTypes().size(), is(10));
assertTrue(discovery.getSupportedThingTypes().contains(THING_TYPE_DIMMABLE_LIGHT));
assertTrue(discovery.getSupportedThingTypes().contains(THING_TYPE_COLOR_TEMP_LIGHT));
assertTrue(discovery.getSupportedThingTypes().contains(THING_TYPE_COLOR_LIGHT));
@@ -102,6 +103,7 @@ public class TradfriDiscoveryServiceTest {
assertTrue(discovery.getSupportedThingTypes().contains(THING_TYPE_OPEN_CLOSE_REMOTE_CONTROL));
assertTrue(discovery.getSupportedThingTypes().contains(THING_TYPE_ONOFF_PLUG));
assertTrue(discovery.getSupportedThingTypes().contains(THING_TYPE_BLINDS));
assertTrue(discovery.getSupportedThingTypes().contains(THING_TYPE_AIR_PURIFIER));
}
@Test
@@ -233,4 +235,20 @@ public class TradfriDiscoveryServiceTest {
assertThat(discoveryResult.getProperties().get(CONFIG_ID), is(65538));
assertThat(discoveryResult.getRepresentationProperty(), is(CONFIG_ID));
}
@Test
public void validDiscoveryResultAirPurifier() {
String json = "{\"3\":{\"0\":\"IKEAofSweden\",\"1\":\"STARKVINDAirpurifier\",\"2\":\"\",\"3\":\"1.0.033\",\"6\":1,\"7\":4364},\"5750\":10,\"9001\":\"Luftreiniger\",\"9002\":1633096623,\"9003\":65548,\"9019\":1,\"9020\":1633096633,\"9054\":0,\"15025\":[{\"5900\":1,\"5902\":2,\"5903\":0,\"5904\":259200,\"5905\":0,\"5906\":0,\"5907\":5,\"5908\":10,\"5909\":2,\"5910\":259198,\"9003\":0}]}";
JsonObject data = JsonParser.parseString(json).getAsJsonObject();
discovery.onUpdate("65548", data);
assertNotNull(discoveryResult);
assertThat(discoveryResult.getFlag(), is(DiscoveryResultFlag.NEW));
assertThat(discoveryResult.getThingUID(), is(new ThingUID("tradfri:0007:1:65548")));
assertThat(discoveryResult.getThingTypeUID(), is(THING_TYPE_AIR_PURIFIER));
assertThat(discoveryResult.getBridgeUID(), is(GATEWAY_THING_UID));
assertThat(discoveryResult.getProperties().get(CONFIG_ID), is(65548));
assertThat(discoveryResult.getRepresentationProperty(), is(CONFIG_ID));
}
}