diff --git a/bundles/org.openhab.binding.daikin/README.md b/bundles/org.openhab.binding.daikin/README.md index ce46aaece3f46..0ae8e72f51b47 100644 --- a/bundles/org.openhab.binding.daikin/README.md +++ b/bundles/org.openhab.binding.daikin/README.md @@ -1,10 +1,10 @@ # Daikin Binding -The Daikin binding allows you to control your Daikin air conditioning units with openHAB. In order to do so, your Daikin air conditioning unit must have a BRP072A42 WiFi adapter installed. +The Daikin binding allows you to control your Daikin air conditioning units with openHAB. In order to do so, your Daikin air conditioning unit must have a BRP072A42 or BRP15B61 WiFi adapter installed. ## Supported Things -Daikin air conditioning units with a BRP072A42 installed. This may work with the older KRP series of wired adapters, but has not been tested with them. +Daikin air conditioning units with a BRP072A42 or BRP15B61 installed. This may work with the older KRP series of wired adapters, but has not been tested with them. ## Discovery @@ -18,7 +18,7 @@ This addon will broadcast messages on your local network looking for Daikin air ## Channels The temperature channels have a precision of one half degree Celsius. - +For the BRP072A42: | Channel Name | Description | |--------------|---------------------------------------------------------------------------------------------| | power | Turns the power on/off for the air conditioning unit. | @@ -27,8 +27,25 @@ The temperature channels have a precision of one half degree Celsius. | outdoortemp | The outdoor temperature as measured by the external part of the air conditioning system. May not be available when unit is off. | | humidity | The indoor humidity as measured by the unit. This is not available on all units. | | mode | The mode set for the unit (AUTO, DEHUMIDIFIER, COLD, HEAT, FAN) | -| fan | The fan speed set for the unit (AUTO, SILENCE, LEVEL_1, LEVEL_2, LEVEL_3, LEVEL_4, LEVEL_5) | - +| fanspeed | The fan speed set for the unit (AUTO, SILENCE, LEVEL_1, LEVEL_2, LEVEL_3, LEVEL_4, LEVEL_5) | + +For the BRP15B61: +| Channel Name | Description | +|-----------------|---------------------------------------------------------------------------------------------| +| power | Turns the power on/off for the air conditioning unit. | +| settemp | The temperature set for the air conditioning unit. | +| indoortemp | The indoor temperature as measured by the unit. | +| outdoortemp | The outdoor temperature as measured by the external part of the air conditioning system. May not be available when unit is off. | +| mode | The mode set for the unit (AUTO, DEHUMIDIFIER, COLD, HEAT, FAN) | +| airbasefanspeed | The fan speed set for the unit (AUTO, LEVEL_1, LEVEL_2, LEVEL_3) | +| zone1 | Turns zone 1 on/off for the air conditioning unit (if a zoned controller is installed. | +| zone2 | Turns zone 2 on/off for the air conditioning unit. | +| zone3 | Turns zone 3 on/off for the air conditioning unit. | +| zone4 | Turns zone 4 on/off for the air conditioning unit. | +| zone5 | Turns zone 5 on/off for the air conditioning unit. | +| zone6 | Turns zone 6 on/off for the air conditioning unit. | +| zone7 | Turns zone 7 on/off for the air conditioning unit. | +| zone8 | Turns zone 8 on/off for the air conditioning unit. | ## Full Example @@ -36,6 +53,7 @@ daikin.things: ``` daikin:ac_unit:living_room_ac [ host="192.168.0.5" ] +daikin:airbase_ac_unit:living_room_ac [ host="192.168.0.5" ] ``` daikin.items: @@ -47,6 +65,16 @@ String DaikinACUnit_Mode { channel="daikin:ac_unit:living_room_ac:mode" } String DaikinACUnit_Fan { channel="daikin:ac_unit:living_room_ac:fanspeed" } Number:Temperature DaikinACUnit_IndoorTemperature { channel="daikin:ac_unit:living_room_ac:indoortemp" } Number:Temperature DaikinACUnit_OutdoorTemperature { channel="daikin:ac_unit:living_room_ac:outdoortemp" } +# Additional items for BRP1B61 +Switch DaikinACUnit_Zone1 { channel="daikin:airbase_ac_unit:living_room_ac:zone1" } +Switch DaikinACUnit_Zone2 { channel="daikin:airbase_ac_unit:living_room_ac:zone2" } +Switch DaikinACUnit_Zone3 { channel="daikin:airbase_ac_unit:living_room_ac:zone3" } +Switch DaikinACUnit_Zone4 { channel="daikin:airbase_ac_unit:living_room_ac:zone4" } +Switch DaikinACUnit_Zone5 { channel="daikin:airbase_ac_unit:living_room_ac:zone5" } +Switch DaikinACUnit_Zone6 { channel="daikin:airbase_ac_unit:living_room_ac:zone6" } +Switch DaikinACUnit_Zone7 { channel="daikin:airbase_ac_unit:living_room_ac:zone7" } +Switch DaikinACUnit_Zone8 { channel="daikin:airbase_ac_unit:living_room_ac:zone8" } + ``` daikin.sitemap: @@ -58,4 +86,14 @@ Selection item=DaikinACUnit_Mode mappings=["AUTO"="Auto", "DEHUMIDIFIER"="Dehumi Selection item=DaikinACUnit_Fan mappings=["AUTO"="Auto", "SILENCE"="Silence", "LEVEL_1"="Level 1", "LEVEL_2"="Level 2", "LEVEL_3"="Level 3", "LEVEL_4"="Level 4", "LEVEL_5"="Level 5"] visibility=[DaikinACUnit_Power==ON] Text item=DaikinACUnit_IndoorTemperature Text item=DaikinACUnit_OutdoorTemperature +# Additional items for BRP1B61 +Switch item=DaikinACUnit_Zone1 visibility=[DaikinACUnit_Power==ON] +Switch item=DaikinACUnit_Zone2 visibility=[DaikinACUnit_Power==ON] +Switch item=DaikinACUnit_Zone3 visibility=[DaikinACUnit_Power==ON] +Switch item=DaikinACUnit_Zone4 visibility=[DaikinACUnit_Power==ON] +Switch item=DaikinACUnit_Zone5 visibility=[DaikinACUnit_Power==ON] +Switch item=DaikinACUnit_Zone6 visibility=[DaikinACUnit_Power==ON] +Switch item=DaikinACUnit_Zone7 visibility=[DaikinACUnit_Power==ON] +Switch item=DaikinACUnit_Zone8 visibility=[DaikinACUnit_Power==ON] + ``` diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/DaikinBindingConstants.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/DaikinBindingConstants.java index 77b1004274e67..db66c28027d7d 100644 --- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/DaikinBindingConstants.java +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/DaikinBindingConstants.java @@ -14,6 +14,8 @@ import java.util.Collections; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.smarthome.core.thing.ThingTypeUID; @@ -23,6 +25,7 @@ * used across the whole binding. * * @author Tim Waterhouse - Initial contribution + * @author Paul Smedley - Modifications to support Airbase Controllers */ @NonNullByDefault public class DaikinBindingConstants { @@ -31,6 +34,7 @@ public class DaikinBindingConstants { // List of all Thing Type UIDs public static final ThingTypeUID THING_TYPE_AC_UNIT = new ThingTypeUID(BINDING_ID, "ac_unit"); + public static final ThingTypeUID THING_TYPE_AIRBASE_AC_UNIT = new ThingTypeUID(BINDING_ID, "airbase_ac_unit"); // List of all Channel ids public static final String CHANNEL_AC_TEMP = "settemp"; @@ -42,5 +46,10 @@ public class DaikinBindingConstants { public static final String CHANNEL_AC_FAN_DIR = "fandir"; public static final String CHANNEL_HUMIDITY = "humidity"; - public static final Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_AC_UNIT); + // additional channels for Airbase Controller + public static final String CHANNEL_AIRBASE_AC_FAN_SPEED = "airbasefanspeed"; + public static final String CHANNEL_AIRBASE_AC_ZONE = "zone"; + + public static final Set SUPPORTED_THING_TYPES_UIDS = Collections + .unmodifiableSet(Stream.of(THING_TYPE_AC_UNIT, THING_TYPE_AIRBASE_AC_UNIT).collect(Collectors.toSet())); } diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/DaikinHandlerFactory.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/DaikinHandlerFactory.java index 5dc80730048ed..d3233fae08de8 100644 --- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/DaikinHandlerFactory.java +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/DaikinHandlerFactory.java @@ -27,6 +27,8 @@ * handlers. * * @author Tim Waterhouse - Initial contribution + * @author Paul Smedley - Modifications to support Airbase Controllers + */ @Component(service = ThingHandlerFactory.class, configurationPid = "binding.daikin") @NonNullByDefault @@ -41,7 +43,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - if (thingTypeUID.equals(DaikinBindingConstants.THING_TYPE_AC_UNIT)) { + if (thingTypeUID.equals(DaikinBindingConstants.THING_TYPE_AC_UNIT) || thingTypeUID.equals(DaikinBindingConstants.THING_TYPE_AIRBASE_AC_UNIT)) { return new DaikinAcUnitHandler(thing); } diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/DaikinWebTargets.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/DaikinWebTargets.java index fdf63b5cb022c..5e7194c9dc5ca 100644 --- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/DaikinWebTargets.java +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/DaikinWebTargets.java @@ -16,17 +16,24 @@ import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.eclipse.smarthome.io.net.http.HttpUtil; import org.openhab.binding.daikin.internal.api.ControlInfo; import org.openhab.binding.daikin.internal.api.SensorInfo; +import org.openhab.binding.daikin.internal.api.airbase.AirbaseControlInfo; +import org.openhab.binding.daikin.internal.api.airbase.AirbaseBasicInfo; +import org.openhab.binding.daikin.internal.api.airbase.AirbaseModelInfo; +import org.openhab.binding.daikin.internal.api.airbase.AirbaseZoneInfo; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Handles performing the actual HTTP requests for communicating with Daiking air conditioning units. + * Handles performing the actual HTTP requests for communicating with Daikin air conditioning units. * * @author Tim Waterhouse - Initial Contribution + * @author Paul Smedley - Modifications to support Airbase Controllers * */ public class DaikinWebTargets { @@ -35,6 +42,14 @@ public class DaikinWebTargets { private String setControlInfoUri; private String getControlInfoUri; private String getSensorInfoUri; + private String setAirbaseControlInfoUri; + private String getAirbaseControlInfoUri; + private String getAirbaseSensorInfoUri; + private String getAirbaseBasicInfoUri; + private String getAirbaseModelInfoUri; + private String getAirbaseZoneInfoUri; + private String setAirbaseZoneInfoUri; + private Logger logger = LoggerFactory.getLogger(DaikinWebTargets.class); public DaikinWebTargets(String ipAddress) { @@ -42,8 +57,18 @@ public DaikinWebTargets(String ipAddress) { setControlInfoUri = baseUri + "aircon/set_control_info"; getControlInfoUri = baseUri + "aircon/get_control_info"; getSensorInfoUri = baseUri + "aircon/get_sensor_info"; + + //Daikin Airbase API + getAirbaseBasicInfoUri = baseUri + "skyfi/common/basic_info"; + setAirbaseControlInfoUri = baseUri + "skyfi/aircon/set_control_info"; + getAirbaseControlInfoUri = baseUri + "skyfi/aircon/get_control_info"; + getAirbaseSensorInfoUri = baseUri + "skyfi/aircon/get_sensor_info"; + getAirbaseModelInfoUri = baseUri + "skyfi/aircon/get_model_info"; + getAirbaseZoneInfoUri = baseUri + "skyfi/aircon/get_zone_setting"; + setAirbaseZoneInfoUri = baseUri + "skyfi/aircon/set_zone_setting"; } + // Standard Daikin API public ControlInfo getControlInfo() throws DaikinCommunicationException { String response = invoke(getControlInfoUri); return ControlInfo.parse(response); @@ -59,6 +84,48 @@ public SensorInfo getSensorInfo() throws DaikinCommunicationException { return SensorInfo.parse(response); } + //Daikin Airbase API + public AirbaseControlInfo getAirbaseControlInfo() throws DaikinCommunicationException { + String response = invoke(getAirbaseControlInfoUri); + return AirbaseControlInfo.parse(response); + } + + public void setAirbaseControlInfo(AirbaseControlInfo info) throws DaikinCommunicationException { + Map queryParams = info.getParamString(); + invoke(setAirbaseControlInfoUri, queryParams); + } + + public SensorInfo getAirbaseSensorInfo() throws DaikinCommunicationException { + String response = invoke(getAirbaseSensorInfoUri); + return SensorInfo.parse(response); + } + + public AirbaseBasicInfo getAirbaseBasicInfo() throws DaikinCommunicationException { + String response = invoke(getAirbaseBasicInfoUri); + return AirbaseBasicInfo.parse(response); + } + + public AirbaseModelInfo getAirbaseModelInfo() throws DaikinCommunicationException { + String response = invoke(getAirbaseModelInfoUri); + return AirbaseModelInfo.parse(response); + } + + public AirbaseZoneInfo getAirbaseZoneInfo() throws DaikinCommunicationException { + String response = invoke(getAirbaseZoneInfoUri); + return AirbaseZoneInfo.parse(response); + } + + public void setAirbaseZoneInfo(AirbaseZoneInfo zoneinfo, AirbaseModelInfo modelinfo) throws DaikinCommunicationException { + long count = IntStream.range(0, zoneinfo.zone.length).filter(idx -> zoneinfo.zone[idx]).count() + modelinfo.commonzone; + logger.debug("Number of open zones: \"{}\"", count); + + Map queryParams = zoneinfo.getParamString(); + if (count >= 1) { + invoke(setAirbaseZoneInfoUri, queryParams); + } + } + + private String invoke(String uri) throws DaikinCommunicationException { return invoke(uri, new HashMap<>()); } diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/ControlInfo.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/ControlInfo.java index 121c9c14a8bc2..ac30c22a7c8f5 100644 --- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/ControlInfo.java +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/ControlInfo.java @@ -28,11 +28,13 @@ * Class for holding the set of parameters used by set and get control info. * * @author Tim Waterhouse - Initial Contribution + * @author Paul Smedley - mods for Daikin Airbase * */ public class ControlInfo { - private static Logger LOGGER = LoggerFactory.getLogger(ControlInfo.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ControlInfo.class); + public String ret; public boolean power = false; public Mode mode = Mode.AUTO; /** Degrees in Celsius. */ @@ -57,6 +59,7 @@ public static ControlInfo parse(String response) { }).collect(Collectors.toMap(x -> x[0], x -> x[1])); ControlInfo info = new ControlInfo(); + info.ret = responseMap.get("ret"); info.power = "1".equals(responseMap.get("pow")); info.mode = Optional.ofNullable(responseMap.get("mode")).flatMap(value -> parseInt(value)) .map(value -> Mode.fromValue(value)).orElse(Mode.AUTO); diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/Enums.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/Enums.java index 00cee261a91d2..c363e18b0f8c4 100644 --- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/Enums.java +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/Enums.java @@ -30,7 +30,7 @@ public enum Mode { HEAT(4), FAN(6); - private static Logger LOGGER = LoggerFactory.getLogger(Mode.class); + private static final Logger LOGGER = LoggerFactory.getLogger(Mode.class); private final int value; Mode(int value) { @@ -64,7 +64,7 @@ public enum FanSpeed { LEVEL_4("6"), LEVEL_5("7"); - private static Logger LOGGER = LoggerFactory.getLogger(FanSpeed.class); + private static final Logger LOGGER = LoggerFactory.getLogger(FanSpeed.class); private final String value; FanSpeed(String value) { @@ -96,7 +96,7 @@ public enum FanMovement { HORIZONTAL(2), VERTICAL_AND_HORIZONTAL(3); - private static Logger LOGGER = LoggerFactory.getLogger(FanMovement.class); + private static final Logger LOGGER = LoggerFactory.getLogger(FanMovement.class); private final int value; FanMovement(int value) { diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseBasicInfo.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseBasicInfo.java new file mode 100644 index 0000000000000..4e30cade0f706 --- /dev/null +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseBasicInfo.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2010-2019 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.daikin.internal.api.airbase; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Holds information from the basic_info call. + * + * @author Paul Smedley - Initial contribution + * + */ +public class AirbaseBasicInfo { + private static final Logger LOGGER = LoggerFactory.getLogger(AirbaseBasicInfo.class); + + public String ssid; + + private AirbaseBasicInfo() { + } + + public static AirbaseBasicInfo parse(String response) { + LOGGER.debug("Parsing string: \"{}\"", response); + + Map responseMap = Arrays.asList(response.split(",")).stream().filter(kv -> kv.contains("=")) + .map(kv -> { + String[] keyValue = kv.split("="); + String key = keyValue[0]; + String value = keyValue.length > 1 ? keyValue[1] : ""; + return new String[] { key, value }; + }).collect(Collectors.toMap(x -> x[0], x -> x[1])); + + AirbaseBasicInfo info = new AirbaseBasicInfo(); + info.ssid = responseMap.get("ssid"); + return info; + } + + public Map getParamString() { + Map params = new HashMap<>(); + params.put("ssid", ssid); + return params; + } + +} diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseControlInfo.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseControlInfo.java new file mode 100644 index 0000000000000..4311c629acc52 --- /dev/null +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseControlInfo.java @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2010-2019 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.daikin.internal.api.airbase; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.openhab.binding.daikin.internal.api.airbase.AirbaseEnums.AirbaseFanMovement; +import org.openhab.binding.daikin.internal.api.airbase.AirbaseEnums.AirbaseFanSpeed; +import org.openhab.binding.daikin.internal.api.airbase.AirbaseEnums.AirbaseMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class for holding the set of parameters used by set and get control info. + * + * @author Tim Waterhouse - Initial Contribution + * @author Paul Smedley - Mods for Daikin Airbase Units + * + */ +public class AirbaseControlInfo { + private static final Logger LOGGER = LoggerFactory.getLogger(AirbaseControlInfo.class); + + public String ret; + public boolean power = false; + public AirbaseMode mode = AirbaseMode.AUTO; + /** Degrees in Celsius. */ + public Optional temp = Optional.empty(); + public AirbaseFanSpeed fanSpeed = AirbaseFanSpeed.AUTO; + public AirbaseFanMovement fanMovement = AirbaseFanMovement.STOPPED; + /* Not supported by all units. Sets the target humidity for dehumidifying. */ + public Optional targetHumidity = Optional.empty(); + + private AirbaseControlInfo() { + } + + public static AirbaseControlInfo parse(String response) { + LOGGER.debug("Parsing string: \"{}\"", response); + + Map responseMap = Arrays.asList(response.split(",")).stream().filter(kv -> kv.contains("=")) + .map(kv -> { + String[] keyValue = kv.split("="); + String key = keyValue[0]; + String value = keyValue.length > 1 ? keyValue[1] : ""; + return new String[] { key, value }; + }).collect(Collectors.toMap(x -> x[0], x -> x[1])); + + AirbaseControlInfo info = new AirbaseControlInfo(); + info.ret = responseMap.get("ret"); + info.power = "1".equals(responseMap.get("pow")); + info.mode = Optional.ofNullable(responseMap.get("mode")).flatMap(value -> parseInt(value)) + .map(value -> AirbaseMode.fromValue(value)).orElse(AirbaseMode.AUTO); + info.temp = Optional.ofNullable(responseMap.get("stemp")).flatMap(value -> parseDouble(value)); + info.fanSpeed = Optional.ofNullable(responseMap.get("f_rate")).map(value -> AirbaseFanSpeed.fromValue(value)) + .orElse(AirbaseFanSpeed.AUTO); + info.fanMovement = Optional.ofNullable(responseMap.get("f_dir")).flatMap(value -> parseInt(value)) + .map(value -> AirbaseFanMovement.fromValue(value)).orElse(AirbaseFanMovement.STOPPED); + info.targetHumidity = Optional.ofNullable(responseMap.get("shum")).flatMap(value -> parseInt(value)); + return info; + } + + public Map getParamString() { + Map params = new HashMap<>(); + params.put("pow", power ? "1" : "0"); + params.put("mode", Integer.toString(mode.getValue())); + params.put("f_rate", fanSpeed.getValue()); + params.put("f_dir", Integer.toString(fanMovement.getValue())); + params.put("stemp", temp.orElse(20.0).toString()); + params.put("shum", targetHumidity.map(value -> value.toString()).orElse("")); + + return params; + } + + private static Optional parseDouble(String value) { + try { + return Optional.of(Double.parseDouble(value)); + } catch (NumberFormatException e) { + return Optional.empty(); + } + } + + private static Optional parseInt(String value) { + try { + return Optional.of(Integer.parseInt(value)); + } catch (Exception e) { + return Optional.empty(); + } + } +} diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseEnums.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseEnums.java new file mode 100644 index 0000000000000..ea80a9bb27227 --- /dev/null +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseEnums.java @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2010-2019 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.daikin.internal.api.airbase; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Container class for enums related to Daikin Airbase A/C systems + * + * @author Tim Waterhouse - Initial contribution + * @author Paul Smedley - Mods for Daikin Airbase Units + * + */ +public class AirbaseEnums { + public enum AirbaseMode { + UNKNOWN(-1), + FAN(0), + HEAT(1), + COLD(2), + DRY(7), + AUTO(3); + + private static final Logger LOGGER = LoggerFactory.getLogger(AirbaseMode.class); + private final int value; + + AirbaseMode(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static AirbaseMode fromValue(int value) { + for (AirbaseMode m : AirbaseMode.values()) { + if (m.getValue() == value) { + return m; + } + } + + LOGGER.debug("Unexpected Mode value of \"{}\"", value); + + // Default to auto + return AUTO; + } + } + + public enum AirbaseFanSpeed { + AUTO("A"), + LEVEL_1("1"), + LEVEL_2("3"), + LEVEL_3("5"); + + private static final Logger LOGGER = LoggerFactory.getLogger(AirbaseFanSpeed.class); + private final String value; + + AirbaseFanSpeed(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public static AirbaseFanSpeed fromValue(String value) { + for (AirbaseFanSpeed m : AirbaseFanSpeed.values()) { + if (m.getValue().equals(value)) { + return m; + } + } + + LOGGER.debug("Unexpected FanSpeed value of \"{}\"", value); + + // Default to auto + return AUTO; + } + } + + public enum AirbaseFanMovement { + UNKNOWN(-1), + STOPPED(0), + VERTICAL(1), + HORIZONTAL(2), + VERTICAL_AND_HORIZONTAL(3); + + private static final Logger LOGGER = LoggerFactory.getLogger(AirbaseFanMovement.class); + private final int value; + + AirbaseFanMovement(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static AirbaseFanMovement fromValue(int value) { + for (AirbaseFanMovement m : AirbaseFanMovement.values()) { + if (m.getValue() == value) { + return m; + } + } + + LOGGER.debug("Unexpected FanMovement value of \"{}\"", value); + + // Default to stopped + return STOPPED; + } + } +} diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseModelInfo.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseModelInfo.java new file mode 100644 index 0000000000000..504000b15bbd2 --- /dev/null +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseModelInfo.java @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2010-2019 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.daikin.internal.api.airbase; + +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class for holding the set of parameters used by get model info. + * + * @author Paul Smedley - Initial Contribution + * + */ +public class AirbaseModelInfo { + private static final Logger LOGGER = LoggerFactory.getLogger(AirbaseModelInfo.class); + + public String ret; + public Integer zonespresent; + public Integer commonzone; + + private AirbaseModelInfo() { + } + + public static AirbaseModelInfo parse(String response) { + LOGGER.debug("Parsing string: \"{}\"", response); + + Map responseMap = Arrays.asList(response.split(",")).stream().filter(kv -> kv.contains("=")) + .map(kv -> { + String[] keyValue = kv.split("="); + String key = keyValue[0]; + String value = keyValue.length > 1 ? keyValue[1] : ""; + return new String[] { key, value }; + }).collect(Collectors.toMap(x -> x[0], x -> x[1])); + + AirbaseModelInfo info = new AirbaseModelInfo(); + info.ret = responseMap.get("ret"); + info.zonespresent = Integer.parseInt(responseMap.get("en_zone")); + info.commonzone = Integer.parseInt(responseMap.get("en_common_zone")); + return info; + } + +} diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseZoneInfo.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseZoneInfo.java new file mode 100644 index 0000000000000..daffee000cde1 --- /dev/null +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseZoneInfo.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2010-2019 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.daikin.internal.api.airbase; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.stream.IntStream; +import java.util.Map; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Holds information from the basic_info call. + * + * @author Paul Smedley - Initial contribution + * + */ +public class AirbaseZoneInfo { + private static final Logger LOGGER = LoggerFactory.getLogger(AirbaseZoneInfo.class); + + public String zonenames; + public boolean zone[] = new boolean[9]; + + private AirbaseZoneInfo() { + } + + public static AirbaseZoneInfo parse(String response) { + LOGGER.debug("Parsing string: \"{}\"", response); + + Map responseMap = Arrays.asList(response.split(",")).stream().filter(kv -> kv.contains("=")) + .map(kv -> { + String[] keyValue = kv.split("="); + String key = keyValue[0]; + String value = keyValue.length > 1 ? keyValue[1] : ""; + return new String[] { key, value }; + }).collect(Collectors.toMap(x -> x[0], x -> x[1])); + + AirbaseZoneInfo info = new AirbaseZoneInfo(); + info.zonenames = responseMap.get("zone_name"); + String zoneinfo = responseMap.get("zone_onoff"); + + String[] Zones = zoneinfo.split("%3b"); + + for (int i = 1; i < 9; i++) + info.zone[i] = "1".equals(Zones[i-1]); + return info; + } + + public Map getParamString() { + Map params = new LinkedHashMap<>(); + String onoffstring = IntStream.range(0, zone.length).mapToObj(idx -> zone[idx] ? "1" : "0").collect(Collectors.joining("%3b")); + params.put("zone_name", zonenames); + params.put("zone_onoff", onoffstring); + + return params; + } + +} diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/discovery/DaikinACUnitDiscoveryService.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/discovery/DaikinACUnitDiscoveryService.java index 36e2c2e6693a4..a31e9cb50b490 100644 --- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/discovery/DaikinACUnitDiscoveryService.java +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/discovery/DaikinACUnitDiscoveryService.java @@ -32,6 +32,9 @@ import org.openhab.binding.daikin.internal.DaikinBindingConstants; import org.openhab.binding.daikin.internal.DaikinWebTargets; import org.openhab.binding.daikin.internal.config.DaikinConfiguration; +import org.openhab.binding.daikin.internal.api.ControlInfo; +import org.openhab.binding.daikin.internal.api.airbase.AirbaseBasicInfo; +import org.openhab.binding.daikin.internal.api.airbase.AirbaseControlInfo; import org.osgi.service.component.annotations.Component; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,6 +43,7 @@ * Discovery service for Daikin AC units. * * @author Tim Waterhouse - Initial contribution + * @author Paul Smedley - Modifications to support Airbase Controllers * */ @Component(service = DiscoveryService.class) @@ -121,20 +125,37 @@ private boolean receivePacketAndDiscover(DatagramSocket socket) { String host = incomingPacket.getAddress().toString().substring(1); logger.debug("Received packet from {}", host); - new DaikinWebTargets(host).getControlInfo(); - - ThingUID thingUID = new ThingUID(DaikinBindingConstants.THING_TYPE_AC_UNIT, host.replace('.', '_')); - DiscoveryResult result = DiscoveryResultBuilder.create(thingUID) - .withProperty(DaikinConfiguration.HOST, host).withLabel("Daikin AC Unit (" + host + ")") - .build(); + // look for Daikin controller + ControlInfo controlInfo = new DaikinWebTargets(host).getControlInfo(); + if (controlInfo.ret.equals("OK")) { + ThingUID thingUID = new ThingUID(DaikinBindingConstants.THING_TYPE_AC_UNIT, host.replace('.', '_')); + DiscoveryResult result = DiscoveryResultBuilder.create(thingUID) + .withProperty(DaikinConfiguration.HOST, host).withLabel("Daikin AC Unit (" + host + ")") + .build(); + + logger.debug("Successfully discovered host {}", host); + thingDiscovered(result); + return true; + } + // look for Daikin Airbase controller + AirbaseControlInfo airbaseControlInfo = new DaikinWebTargets(host).getAirbaseControlInfo(); + if (airbaseControlInfo.ret.equals("OK")) { + AirbaseBasicInfo basicInfo = new DaikinWebTargets(host).getAirbaseBasicInfo(); + ThingUID thingUID = new ThingUID(DaikinBindingConstants.THING_TYPE_AIRBASE_AC_UNIT, basicInfo.ssid); + DiscoveryResult result = DiscoveryResultBuilder.create(thingUID) + .withProperty(DaikinConfiguration.HOST, host).withLabel("Daikin Airbase AC Unit (" + host + ")") + .build(); + + logger.debug("Successfully discovered host {}", host); + thingDiscovered(result); + return true; + } - logger.debug("Successfully discovered host {}", host); - thingDiscovered(result); } catch (Exception e) { return false; } - - return true; + // Shouldn't get here unless we don't detect a controller + return false; } private List getBroadcastAddresses() { diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinAcUnitHandler.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinAcUnitHandler.java index 8b8962591c6be..ee6eb93d0b06b 100644 --- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinAcUnitHandler.java +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinAcUnitHandler.java @@ -16,6 +16,7 @@ import java.util.Optional; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; import javax.measure.quantity.Temperature; @@ -26,6 +27,7 @@ import org.eclipse.smarthome.core.library.unit.SIUnits; import org.eclipse.smarthome.core.thing.ChannelUID; import org.eclipse.smarthome.core.thing.Thing; +import org.eclipse.smarthome.core.thing.ThingTypeUID; import org.eclipse.smarthome.core.thing.ThingStatus; import org.eclipse.smarthome.core.thing.ThingStatusDetail; import org.eclipse.smarthome.core.thing.binding.BaseThingHandler; @@ -40,6 +42,12 @@ import org.openhab.binding.daikin.internal.api.Enums.FanSpeed; import org.openhab.binding.daikin.internal.api.Enums.Mode; import org.openhab.binding.daikin.internal.api.SensorInfo; +import org.openhab.binding.daikin.internal.api.airbase.AirbaseEnums.AirbaseFanSpeed; +import org.openhab.binding.daikin.internal.api.airbase.AirbaseEnums.AirbaseMode; +import org.openhab.binding.daikin.internal.api.airbase.AirbaseControlInfo; +import org.openhab.binding.daikin.internal.api.airbase.AirbaseModelInfo; +import org.openhab.binding.daikin.internal.api.airbase.AirbaseZoneInfo; + import org.openhab.binding.daikin.internal.config.DaikinConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,6 +56,7 @@ * Handles communicating with a Daikin air conditioning unit. * * @author Tim Waterhouse - Initial Contribution + * @author Paul Smedley - Modifications to support Airbase Controllers * */ public class DaikinAcUnitHandler extends BaseThingHandler { @@ -75,6 +84,9 @@ public void handleCommand(ChannelUID channelUID, Command command) { } private void handleCommandInternal(ChannelUID channelUID, Command command) throws DaikinCommunicationException { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + String channelId = channelUID.getId(); + switch (channelUID.getId()) { case DaikinBindingConstants.CHANNEL_AC_POWER: if (command instanceof OnOffType) { @@ -87,6 +99,12 @@ private void handleCommandInternal(ChannelUID channelUID, Command command) throw return; } break; + case DaikinBindingConstants.CHANNEL_AIRBASE_AC_FAN_SPEED: + if (command instanceof StringType) { + changeAirbaseFanSpeed(AirbaseFanSpeed.valueOf(((StringType) command).toString())); + return; + } + break; case DaikinBindingConstants.CHANNEL_AC_FAN_SPEED: if (command instanceof StringType) { changeFanSpeed(FanSpeed.valueOf(((StringType) command).toString())); @@ -101,12 +119,23 @@ private void handleCommandInternal(ChannelUID channelUID, Command command) throw break; case DaikinBindingConstants.CHANNEL_AC_MODE: if (command instanceof StringType) { - changeMode(Mode.valueOf(((StringType) command).toString())); + + if (thingTypeUID.equals(DaikinBindingConstants.THING_TYPE_AC_UNIT)) + changeMode(Mode.valueOf(((StringType) command).toString())); + else + changeAirbaseMode(AirbaseMode.valueOf(((StringType) command).toString())); return; } break; } - + /* additional controls for Daikin Airbase */ + if (channelId.startsWith(DaikinBindingConstants.CHANNEL_AIRBASE_AC_ZONE)) { + int zoneNumber = Integer.parseInt(channelUID.getId().substring(4)); + if (command instanceof OnOffType) { + changeZone(zoneNumber, command == OnOffType.ON); + return; + } + } logger.warn("Received command of wrong type for thing '{}' on channel {}", thing.getUID().getAsString(), channelUID.getId()); } @@ -155,7 +184,14 @@ private synchronized void stopPoll() { private synchronized void poll() { try { logger.debug("Polling for state"); - pollStatus(); + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (thingTypeUID.equals(DaikinBindingConstants.THING_TYPE_AC_UNIT)) { + pollStatus(); + } else { + pollAirbaseStatus(); + } + } catch (IOException e) { logger.debug("Could not connect to Daikin controller", e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); @@ -191,6 +227,29 @@ private void pollStatus() throws IOException { } } + private void pollAirbaseStatus() throws IOException { + AirbaseControlInfo controlInfo = webTargets.getAirbaseControlInfo(); + updateStatus(ThingStatus.ONLINE); + if (controlInfo != null) { + updateState(DaikinBindingConstants.CHANNEL_AC_POWER, controlInfo.power ? OnOffType.ON : OnOffType.OFF); + updateTemperatureChannel(DaikinBindingConstants.CHANNEL_AC_TEMP, controlInfo.temp); + updateState(DaikinBindingConstants.CHANNEL_AC_MODE, new StringType(controlInfo.mode.name())); + updateState(DaikinBindingConstants.CHANNEL_AIRBASE_AC_FAN_SPEED, new StringType(controlInfo.fanSpeed.name())); + } + + SensorInfo sensorInfo = webTargets.getAirbaseSensorInfo(); + if (sensorInfo != null) { + updateTemperatureChannel(DaikinBindingConstants.CHANNEL_INDOOR_TEMP, sensorInfo.indoortemp); + + updateTemperatureChannel(DaikinBindingConstants.CHANNEL_OUTDOOR_TEMP, sensorInfo.outdoortemp); + } + AirbaseZoneInfo zoneInfo = webTargets.getAirbaseZoneInfo(); + if (zoneInfo != null) { + IntStream.range(0, zoneInfo.zone.length).forEach( + idx -> updateState(DaikinBindingConstants.CHANNEL_AIRBASE_AC_ZONE + idx, OnOffType.from(zoneInfo.zone[idx]))); + } + } + private void updateTemperatureChannel(String channel, Optional maybeTemperature) { updateState(channel, maybeTemperature. map(t -> new QuantityType(new DecimalType(t), SIUnits.CELSIUS)) @@ -198,9 +257,20 @@ maybeTemperature. map(t -> new QuantityType(new DecimalType( } private void changePower(boolean power) throws DaikinCommunicationException { - ControlInfo info = webTargets.getControlInfo(); - info.power = power; - webTargets.setControlInfo(info); + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (thingTypeUID.equals(DaikinBindingConstants.THING_TYPE_AC_UNIT)) { + // handle Daikin + ControlInfo info = webTargets.getControlInfo(); + info.power = power; + webTargets.setControlInfo(info); + } else { + // handle Airbase + AirbaseControlInfo info = webTargets.getAirbaseControlInfo(); + info.power = power; + webTargets.setAirbaseControlInfo(info); + + } } /** @@ -219,9 +289,19 @@ private boolean changeSetPoint(Command command) throws DaikinCommunicationExcept // Only half degree increments are allowed, all others are silently rejected by the A/C units newTemperature = Math.round(newTemperature * 2) / 2.0; - ControlInfo info = webTargets.getControlInfo(); - info.temp = Optional.of(newTemperature); - webTargets.setControlInfo(info); + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (thingTypeUID.equals(DaikinBindingConstants.THING_TYPE_AC_UNIT)) { + // handle Daikin + ControlInfo info = webTargets.getControlInfo(); + info.temp = Optional.of(newTemperature); + webTargets.setControlInfo(info); + } else { + // handle Airbase + AirbaseControlInfo info = webTargets.getAirbaseControlInfo(); + info.temp = Optional.of(newTemperature); + webTargets.setAirbaseControlInfo(info); + } return true; } @@ -232,15 +312,39 @@ private void changeMode(Mode mode) throws DaikinCommunicationException { webTargets.setControlInfo(info); } + private void changeAirbaseMode(AirbaseMode mode) throws DaikinCommunicationException { + AirbaseControlInfo info = webTargets.getAirbaseControlInfo(); + info.mode = mode; + webTargets.setAirbaseControlInfo(info); + } + private void changeFanSpeed(FanSpeed fanSpeed) throws DaikinCommunicationException { ControlInfo info = webTargets.getControlInfo(); info.fanSpeed = fanSpeed; webTargets.setControlInfo(info); } + private void changeAirbaseFanSpeed(AirbaseFanSpeed fanSpeed) throws DaikinCommunicationException { + AirbaseControlInfo info = webTargets.getAirbaseControlInfo(); + info.fanSpeed = fanSpeed; + webTargets.setAirbaseControlInfo(info); + } + + // FanDir only supported on standard Daikin controllers, so don't need to detect what kind of thing we have private void changeFanDir(FanMovement fanDir) throws DaikinCommunicationException { ControlInfo info = webTargets.getControlInfo(); info.fanMovement = fanDir; webTargets.setControlInfo(info); } + + // Zones only supported on Airbase, so don't need to detect what kind of thing we have + private void changeZone(int zone, boolean command) throws DaikinCommunicationException { + AirbaseZoneInfo info = webTargets.getAirbaseZoneInfo(); + AirbaseModelInfo modelInfo = webTargets.getAirbaseModelInfo(); + info.zone[zone] = command; + if (modelInfo.zonespresent >=zone) { + webTargets.setAirbaseZoneInfo(info, modelInfo); + } + } + } diff --git a/bundles/org.openhab.binding.daikin/src/main/resources/ESH-INF/config/config.xml b/bundles/org.openhab.binding.daikin/src/main/resources/ESH-INF/config/config.xml new file mode 100644 index 0000000000000..9fdc73f6c9ee1 --- /dev/null +++ b/bundles/org.openhab.binding.daikin/src/main/resources/ESH-INF/config/config.xml @@ -0,0 +1,20 @@ + + + + + + + The Host address of the Daikin AC unit. + network_address + + + + The number of seconds between fetches of the AC unit state. + 60 + + + + diff --git a/bundles/org.openhab.binding.daikin/src/main/resources/ESH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.daikin/src/main/resources/ESH-INF/thing/thing-types.xml index 137c224108e72..07666ceeaf24e 100644 --- a/bundles/org.openhab.binding.daikin/src/main/resources/ESH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.daikin/src/main/resources/ESH-INF/thing/thing-types.xml @@ -18,18 +18,32 @@ - - - - The Host address of the Daikin AC unit. - network_address - - - - The number of seconds between fetches of the AC unit state. - 60 - - + + + + + + Daikin Airbase AC Unit + + + + + + + + + + + + + + + + + + + + @@ -115,4 +129,66 @@ + + String + + Current fan speed setting of the AC unit + + + + + + + + + + + + Switch + + Zone 1 for the AC unit + + + + Switch + + Zone 2 for the AC unit + + + + Switch + + Zone 3 for the AC unit + + + + Switch + + Zone 4 for the AC unit + + + + Switch + + Zone 5 for the AC unit + + + + Switch + + Zone 6 for the AC unit + + + + Switch + + Zone 7 for the AC unit + + + + Switch + + Zone 8 for the AC unit + +