added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.daikin-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-daikin" description="Daikin Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.daikin/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) 2010-2020 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;
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.openhab.core.thing.ThingTypeUID;
/**
* The {@link DaikinBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Tim Waterhouse <tim@timwaterhouse.com> - Initial contribution
* @author Paul Smedley <paul@smedley.id.au> - Modifications to support Airbase Controllers
*/
@NonNullByDefault
public class DaikinBindingConstants {
private static final String BINDING_ID = "daikin";
// 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";
public static final String CHANNEL_INDOOR_TEMP = "indoortemp";
public static final String CHANNEL_OUTDOOR_TEMP = "outdoortemp";
public static final String CHANNEL_AC_POWER = "power";
public static final String CHANNEL_AC_MODE = "mode";
public static final String CHANNEL_AC_HOMEKITMODE = "homekitmode";
public static final String CHANNEL_AC_FAN_SPEED = "fanspeed";
public static final String CHANNEL_AC_FAN_DIR = "fandir";
public static final String CHANNEL_HUMIDITY = "humidity";
public static final String CHANNEL_CMP_FREQ = "cmpfrequency";
// Prefix and channel id format for energy - currentyear
public static final String CHANNEL_ENERGY_HEATING_CURRENTYEAR_PREFIX = "energyheatingcurrentyear";
public static final String CHANNEL_ENERGY_COOLING_CURRENTYEAR_PREFIX = "energycoolingcurrentyear";
public static final String CHANNEL_ENERGY_STRING_FORMAT = "%s-%d";
public static final String CHANNEL_AC_SPECIALMODE = "specialmode";
public static final String CHANNEL_AC_SPECIALMODE_POWERFUL = "specialmode-powerful";
// 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<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(THING_TYPE_AC_UNIT, THING_TYPE_AIRBASE_AC_UNIT).collect(Collectors.toSet()));
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2010-2020 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;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Exception for when an unexpected response is received from the Daikin controller.
*
* @author Tim Waterhouse <tim@timwaterhouse.com> - Initial contribution
*
*/
@NonNullByDefault
public class DaikinCommunicationException extends IOException {
private static final long serialVersionUID = 529232811860854017L;
public DaikinCommunicationException(String message) {
super(message);
}
public DaikinCommunicationException(Throwable ex) {
super(ex);
}
public DaikinCommunicationException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2020 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;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Exception for when a 403 Forbidden error is received from the Daikin controller.
*
* @author Jimmy Tanagra - Initial contribution
*
*/
@NonNullByDefault
public class DaikinCommunicationForbiddenException extends DaikinCommunicationException {
private static final long serialVersionUID = 1L;
public DaikinCommunicationForbiddenException(String message) {
super(message);
}
public DaikinCommunicationForbiddenException(Throwable ex) {
super(ex);
}
public DaikinCommunicationForbiddenException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2020 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;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* Dynamic provider of state options while leaving other state description fields as original.
*
* @author Jimmy Tanagra - Initial contribution
*/
@Component(service = { DynamicStateDescriptionProvider.class, DaikinDynamicStateDescriptionProvider.class })
@NonNullByDefault
public class DaikinDynamicStateDescriptionProvider extends BaseDynamicStateDescriptionProvider {
@Reference
protected void setChannelTypeI18nLocalizationService(
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
}
protected void unsetChannelTypeI18nLocalizationService(
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
this.channelTypeI18nLocalizationService = null;
}
}

View File

@@ -0,0 +1,67 @@
/**
* Copyright (c) 2010-2020 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;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.daikin.internal.handler.DaikinAcUnitHandler;
import org.openhab.binding.daikin.internal.handler.DaikinAirbaseUnitHandler;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link DaikinHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Tim Waterhouse <tim@timwaterhouse.com> - Initial contribution
* @author Paul Smedley <paul@smedley.id.au> - Modifications to support Airbase Controllers
*
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.daikin")
@NonNullByDefault
public class DaikinHandlerFactory extends BaseThingHandlerFactory {
private final DaikinDynamicStateDescriptionProvider stateDescriptionProvider;
private final @Nullable HttpClient httpClient;
@Activate
public DaikinHandlerFactory(@Reference DaikinDynamicStateDescriptionProvider stateDescriptionProvider,
@Reference DaikinHttpClientFactory httpClientFactory) {
this.stateDescriptionProvider = stateDescriptionProvider;
this.httpClient = httpClientFactory.getHttpClient();
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return DaikinBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(DaikinBindingConstants.THING_TYPE_AC_UNIT)) {
return new DaikinAcUnitHandler(thing, stateDescriptionProvider, httpClient);
} else if (thingTypeUID.equals(DaikinBindingConstants.THING_TYPE_AIRBASE_AC_UNIT)) {
return new DaikinAirbaseUnitHandler(thing, stateDescriptionProvider, httpClient);
}
return null;
}
}

View File

@@ -0,0 +1,35 @@
/**
* Copyright (c) 2010-2020 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;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
/**
* Factory class to create Jetty http clients
*
* @author Jimmy Tanagra - Initial contribution
*/
@NonNullByDefault
public interface DaikinHttpClientFactory {
/**
* Returns the shared Jetty http client. You must not call any setter methods or {@code stop()} on it.
* The returned client is already started.
*
* @return the shared Jetty http client
*/
@Nullable
HttpClient getHttpClient();
}

View File

@@ -0,0 +1,75 @@
/**
* Copyright (c) 2010-2020 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;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Factory class to create Jetty web clients
*
* Some Daikin controllers communicate via https using a custom common name,
* and they are accessed using an ip address.
*
* The core HttpClientFactory creates a HttpClient that will fail because of this.
* This factory creates a HttpClient with SslContextFactory(true)
* which will accept any ssl certificate without checking for common name mismatches.
*
* @author Jimmy Tanagra - Initial contribution
*/
@Component
@NonNullByDefault
public class DaikinHttpClientFactoryImpl implements DaikinHttpClientFactory {
private final Logger logger = LoggerFactory.getLogger(DaikinHttpClientFactoryImpl.class);
private @Nullable HttpClient httpClient;
@Deactivate
protected void deactivate() {
if (httpClient != null) {
try {
httpClient.stop();
logger.debug("Daikin http client stopped");
} catch (Exception e) {
logger.debug("error while stopping Daikin http client", e);
}
httpClient = null;
}
}
@Override
public @Nullable HttpClient getHttpClient() {
initialize();
return httpClient;
}
private synchronized void initialize() {
if (httpClient == null) {
httpClient = new HttpClient(new SslContextFactory(true));
try {
httpClient.start();
logger.debug("Daikin http client started");
} catch (Exception e) {
logger.warn("Could not start Daikin http client", e);
httpClient = null;
}
}
}
}

View File

@@ -0,0 +1,242 @@
/**
* Copyright (c) 2010-2020 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;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.daikin.internal.api.BasicInfo;
import org.openhab.binding.daikin.internal.api.ControlInfo;
import org.openhab.binding.daikin.internal.api.EnergyInfoYear;
import org.openhab.binding.daikin.internal.api.Enums.SpecialModeKind;
import org.openhab.binding.daikin.internal.api.SensorInfo;
import org.openhab.binding.daikin.internal.api.airbase.AirbaseBasicInfo;
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.core.io.net.http.HttpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handles performing the actual HTTP requests for communicating with Daikin air conditioning units.
*
* @author Tim Waterhouse - Initial Contribution
* @author Paul Smedley <paul@smedley.id.au> - Modifications to support Airbase Controllers
* @author Jimmy Tanagra - Add support for https and Daikin's uuid authentication
*
*/
@NonNullByDefault
public class DaikinWebTargets {
private static final int TIMEOUT_MS = 30000;
private String getBasicInfoUri;
private String setControlInfoUri;
private String getControlInfoUri;
private String getSensorInfoUri;
private String registerUuidUri;
private String getEnergyInfoYearUri;
private String setSpecialModeUri;
private String setAirbaseControlInfoUri;
private String getAirbaseControlInfoUri;
private String getAirbaseSensorInfoUri;
private String getAirbaseBasicInfoUri;
private String getAirbaseModelInfoUri;
private String getAirbaseZoneInfoUri;
private String setAirbaseZoneInfoUri;
private @Nullable String uuid;
private final @Nullable HttpClient httpClient;
private Logger logger = LoggerFactory.getLogger(DaikinWebTargets.class);
public DaikinWebTargets(@Nullable HttpClient httpClient, @Nullable String host, @Nullable Boolean secure,
@Nullable String uuid) {
this.httpClient = httpClient;
this.uuid = uuid;
String baseUri = (secure != null && secure.booleanValue() ? "https://" : "http://") + host + "/";
getBasicInfoUri = baseUri + "common/basic_info";
setControlInfoUri = baseUri + "aircon/set_control_info";
getControlInfoUri = baseUri + "aircon/get_control_info";
getSensorInfoUri = baseUri + "aircon/get_sensor_info";
registerUuidUri = baseUri + "common/register_terminal";
getEnergyInfoYearUri = baseUri + "aircon/get_year_power_ex";
setSpecialModeUri = baseUri + "aircon/set_special_mode";
// 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 BasicInfo getBasicInfo() throws DaikinCommunicationException {
String response = invoke(getBasicInfoUri);
return BasicInfo.parse(response);
}
public ControlInfo getControlInfo() throws DaikinCommunicationException {
String response = invoke(getControlInfoUri);
return ControlInfo.parse(response);
}
public void setControlInfo(ControlInfo info) throws DaikinCommunicationException {
Map<String, String> queryParams = info.getParamString();
invoke(setControlInfoUri, queryParams);
}
public SensorInfo getSensorInfo() throws DaikinCommunicationException {
String response = invoke(getSensorInfoUri);
return SensorInfo.parse(response);
}
public void registerUuid(String key) throws DaikinCommunicationException {
Map<String, String> params = new HashMap<>();
params.put("key", key);
String response = invoke(registerUuidUri, params);
logger.debug("registerUuid result: {}", response);
}
public EnergyInfoYear getEnergyInfoYear() throws DaikinCommunicationException {
String response = invoke(getEnergyInfoYearUri);
return EnergyInfoYear.parse(response);
}
public boolean setSpecialMode(SpecialModeKind specialModeKind, boolean state) throws DaikinCommunicationException {
Map<String, String> queryParams = new HashMap<>();
queryParams.put("spmode_kind", String.valueOf(specialModeKind.getValue()));
queryParams.put("set_spmode", state ? "1" : "0");
String response = invoke(setSpecialModeUri, queryParams);
return !response.contains("ret=OK");
}
// Daikin Airbase API
public AirbaseControlInfo getAirbaseControlInfo() throws DaikinCommunicationException {
String response = invoke(getAirbaseControlInfoUri);
return AirbaseControlInfo.parse(response);
}
public void setAirbaseControlInfo(AirbaseControlInfo info) throws DaikinCommunicationException {
Map<String, String> 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) throws DaikinCommunicationException {
Map<String, String> queryParams = zoneinfo.getParamString();
invoke(setAirbaseZoneInfoUri, queryParams);
}
private String invoke(String uri) throws DaikinCommunicationException {
return invoke(uri, new HashMap<>());
}
private String invoke(String uri, Map<String, String> params) throws DaikinCommunicationException {
String uriWithParams = uri + paramsToQueryString(params);
logger.debug("Calling url: {}", uriWithParams);
String response;
synchronized (this) {
try {
if (httpClient != null) {
response = executeUrl(uriWithParams);
} else {
// a fall back method
logger.debug("Using HttpUtil fall scback");
response = HttpUtil.executeUrl("GET", uriWithParams, TIMEOUT_MS);
}
} catch (DaikinCommunicationException ex) {
throw ex;
} catch (IOException ex) {
// Response will also be set to null if parsing in executeUrl fails so we use null here to make the
// error check below consistent.
response = null;
}
}
if (response == null) {
throw new DaikinCommunicationException("Daikin controller returned error while invoking " + uriWithParams);
}
return response;
}
private String executeUrl(String url) throws DaikinCommunicationException {
try {
Request request = httpClient.newRequest(url).method(HttpMethod.GET).timeout(TIMEOUT_MS,
TimeUnit.MILLISECONDS);
if (uuid != null) {
request.header("X-Daikin-uuid", uuid);
logger.debug("Header: X-Daikin-uuid: {}", uuid);
}
ContentResponse response = request.send();
if (response.getStatus() == HttpStatus.FORBIDDEN_403) {
throw new DaikinCommunicationForbiddenException("Daikin controller access denied. Check uuid/key.");
}
if (response.getStatus() != HttpStatus.OK_200) {
logger.debug("Daikin controller HTTP status: {} - {}", response.getStatus(), response.getReason());
}
return response.getContentAsString();
} catch (DaikinCommunicationException e) {
throw e;
} catch (Exception e) {
throw new DaikinCommunicationException("Daikin HTTP error", e);
}
}
private String paramsToQueryString(Map<String, String> params) {
if (params.isEmpty()) {
return "";
}
return "?" + params.entrySet().stream().map(param -> param.getKey() + "=" + param.getValue())
.collect(Collectors.joining("&"));
}
}

View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) 2010-2020 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;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Holds information from the basic_info call.
*
* @author Jimy Tanagra - Initial contribution
*
*/
@NonNullByDefault
public class BasicInfo {
private static final Logger logger = LoggerFactory.getLogger(BasicInfo.class);
public String mac = "";
public String ret = "";
public String ssid = "";
private BasicInfo() {
}
public static BasicInfo parse(String response) {
logger.debug("Parsing string: \"{}\"", response);
Map<String, String> responseMap = InfoParser.parse(response);
BasicInfo info = new BasicInfo();
info.mac = Optional.ofNullable(responseMap.get("mac")).orElse("");
info.ret = Optional.ofNullable(responseMap.get("ret")).orElse("");
info.ssid = Optional.ofNullable(responseMap.get("ssid")).orElse("");
return info;
}
public Map<String, String> getParamString() {
Map<String, String> params = new HashMap<>();
params.put("ssid", ssid);
return params;
}
}

View File

@@ -0,0 +1,85 @@
/**
* Copyright (c) 2010-2020 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;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.daikin.internal.api.Enums.FanMovement;
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.Enums.SpecialMode;
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 <paul@smedley.id.au> - mods for Daikin Airbase
*
*/
@NonNullByDefault
public class ControlInfo {
private static final Logger logger = LoggerFactory.getLogger(ControlInfo.class);
public String ret = "";
public boolean power = false;
public Mode mode = Mode.AUTO;
/** Degrees in Celsius. */
public Optional<Double> temp = Optional.empty();
public FanSpeed fanSpeed = FanSpeed.AUTO;
public FanMovement fanMovement = FanMovement.STOPPED;
/* Not supported by all units. Sets the target humidity for dehumidifying. */
public Optional<Integer> targetHumidity = Optional.empty();
public SpecialMode specialMode = SpecialMode.UNKNOWN;
private ControlInfo() {
}
public static ControlInfo parse(String response) {
logger.debug("Parsing string: \"{}\"", response);
Map<String, String> responseMap = InfoParser.parse(response);
ControlInfo info = new ControlInfo();
info.ret = Optional.ofNullable(responseMap.get("ret")).orElse("");
info.power = "1".equals(responseMap.get("pow"));
info.mode = Optional.ofNullable(responseMap.get("mode")).flatMap(value -> InfoParser.parseInt(value))
.map(value -> Mode.fromValue(value)).orElse(Mode.AUTO);
info.temp = Optional.ofNullable(responseMap.get("stemp")).flatMap(value -> InfoParser.parseDouble(value));
info.fanSpeed = Optional.ofNullable(responseMap.get("f_rate")).map(value -> FanSpeed.fromValue(value))
.orElse(FanSpeed.AUTO);
info.fanMovement = Optional.ofNullable(responseMap.get("f_dir")).flatMap(value -> InfoParser.parseInt(value))
.map(value -> FanMovement.fromValue(value)).orElse(FanMovement.STOPPED);
info.targetHumidity = Optional.ofNullable(responseMap.get("shum")).flatMap(value -> InfoParser.parseInt(value));
info.specialMode = Optional.ofNullable(responseMap.get("adv")).map(value -> SpecialMode.fromValue(value))
.orElse(SpecialMode.UNKNOWN);
return info;
}
public Map<String, String> getParamString() {
Map<String, String> 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;
}
}

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) 2010-2020 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;
import java.util.Map;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Holds information from the get_year_power_ex call.
*
* @author Lukas Agethen - Initial contribution
*
*/
@NonNullByDefault
public class EnergyInfoYear {
private static final Logger logger = LoggerFactory.getLogger(EnergyInfoYear.class);
public Optional<Integer[]> energyHeatingThisYear = Optional.empty();
public Optional<Integer[]> energyCoolingThisYear = Optional.empty();
private EnergyInfoYear() {
}
public static EnergyInfoYear parse(String response) {
logger.debug("Parsing string: \"{}\"", response);
Map<String, String> responseMap = InfoParser.parse(response);
EnergyInfoYear info = new EnergyInfoYear();
info.energyHeatingThisYear = Optional.ofNullable(responseMap.get("curr_year_heat"))
.flatMap(value -> InfoParser.parseArrayofInt(value, 12));
info.energyCoolingThisYear = Optional.ofNullable(responseMap.get("curr_year_cool"))
.flatMap(value -> InfoParser.parseArrayofInt(value, 12));
return info;
}
}

View File

@@ -0,0 +1,212 @@
/**
* Copyright (c) 2010-2020 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;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Container class for enums related to Daikin A/C systems
*
* @author Tim Waterhouse <tim@timwaterhouse.com> - Initial contribution
* @author Lukas Agethen - Add special modes
*
*/
@NonNullByDefault
public class Enums {
public enum Mode {
UNKNOWN(-1),
AUTO(0),
DEHUMIDIFIER(2),
COLD(3),
HEAT(4),
FAN(6);
private static final Logger LOGGER = LoggerFactory.getLogger(Mode.class);
private final int value;
Mode(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static Mode fromValue(int value) {
for (Mode m : Mode.values()) {
if (m.getValue() == value) {
return m;
}
}
LOGGER.debug("Unexpected Mode value of \"{}\"", value);
// Default to auto
return AUTO;
}
}
public enum FanSpeed {
AUTO("A"),
SILENCE("B"),
LEVEL_1("3"),
LEVEL_2("4"),
LEVEL_3("5"),
LEVEL_4("6"),
LEVEL_5("7");
private static final Logger LOGGER = LoggerFactory.getLogger(FanSpeed.class);
private final String value;
FanSpeed(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static FanSpeed fromValue(String value) {
for (FanSpeed m : FanSpeed.values()) {
if (m.getValue().equals(value)) {
return m;
}
}
LOGGER.debug("Unexpected FanSpeed value of \"{}\"", value);
// Default to auto
return AUTO;
}
}
public enum FanMovement {
UNKNOWN(-1),
STOPPED(0),
VERTICAL(1),
HORIZONTAL(2),
VERTICAL_AND_HORIZONTAL(3);
private static final Logger LOGGER = LoggerFactory.getLogger(FanMovement.class);
private final int value;
FanMovement(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static FanMovement fromValue(int value) {
for (FanMovement m : FanMovement.values()) {
if (m.getValue() == value) {
return m;
}
}
LOGGER.debug("Unexpected FanMovement value of \"{}\"", value);
// Default to stopped
return STOPPED;
}
}
public enum HomekitMode {
AUTO("auto"),
COOL("cool"),
HEAT("heat"),
OFF("off");
private static final Logger LOGGER = LoggerFactory.getLogger(HomekitMode.class);
private final String value;
HomekitMode(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
public enum SpecialMode {
STREAMER("13"),
ECO("12"),
POWERFUL("2"),
POWERFUL_STREAMER("2/13"),
ECO_STREAMER("12/13"),
OFF(""),
UNKNOWN("??");
private static final Logger LOGGER = LoggerFactory.getLogger(SpecialMode.class);
private final String value;
SpecialMode(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public boolean isPowerfulActive() {
return this.equals(POWERFUL) || this.equals(POWERFUL_STREAMER);
}
public boolean isUndefined() {
return this.equals(UNKNOWN);
}
public static SpecialMode fromValue(String value) {
for (SpecialMode m : SpecialMode.values()) {
if (m.getValue().equals(value)) {
return m;
}
}
LOGGER.debug("Unexpected SpecialMode value of \"{}\"", value);
// Default to UNKNOWN
return UNKNOWN;
}
}
public enum SpecialModeKind {
UNKNOWN(-1),
STREAMER(0),
POWERFUL(1),
ECO(2);
private static final Logger LOGGER = LoggerFactory.getLogger(SpecialModeKind.class);
private final int value;
SpecialModeKind(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static SpecialModeKind fromValue(int value) {
for (SpecialModeKind m : SpecialModeKind.values()) {
if (m.getValue() == value) {
return m;
}
}
LOGGER.debug("Unexpected SpecialModeKind value of \"{}\"", value);
// Default to UNKNOWN
return UNKNOWN;
}
}
}

View File

@@ -0,0 +1,83 @@
/**
* Copyright (c) 2010-2020 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;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Class for parsing the comma separated values and array values returned by the Daikin Controller.
*
* @author Jimmy Tanagra - Initial Contribution
*
*/
@NonNullByDefault
public class InfoParser {
private InfoParser() {
}
public static Map<String, String> parse(String response) {
return Stream.of(response.split(",")).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]));
}
public static Optional<Double> parseDouble(String value) {
if ("-".equals(value)) {
return Optional.empty();
}
try {
return Optional.of(Double.parseDouble(value));
} catch (NumberFormatException e) {
return Optional.empty();
}
}
public static Optional<Integer> parseInt(String value) {
if ("-".equals(value)) {
return Optional.empty();
}
try {
return Optional.of(Integer.parseInt(value));
} catch (NumberFormatException e) {
return Optional.empty();
}
}
public static Optional<Integer[]> parseArrayofInt(String value) {
if ("-".equals(value)) {
return Optional.empty();
}
try {
return Optional.of(Stream.of(value.split("/")).map(val -> Integer.parseInt(val)).toArray(Integer[]::new));
} catch (NumberFormatException e) {
return Optional.empty();
}
}
public static Optional<Integer[]> parseArrayofInt(String value, int expectedArraySize) {
Optional<Integer[]> result = parseArrayofInt(value);
if (result.isPresent() && result.get().length == expectedArraySize) {
return result;
}
return Optional.empty();
}
}

View File

@@ -0,0 +1,56 @@
/**
* Copyright (c) 2010-2020 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;
import java.util.Map;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Holds information from the get_sensor_info call.
*
* @author Tim Waterhouse - Initial contribution
*
*/
@NonNullByDefault
public class SensorInfo {
private static final Logger logger = LoggerFactory.getLogger(SensorInfo.class);
public Optional<Double> indoortemp = Optional.empty();
public Optional<Double> indoorhumidity = Optional.empty();
public Optional<Double> outdoortemp = Optional.empty();
public Optional<Double> compressorfrequency = Optional.empty();
private SensorInfo() {
}
public static SensorInfo parse(String response) {
logger.debug("Parsing string: \"{}\"", response);
Map<String, String> responseMap = InfoParser.parse(response);
SensorInfo info = new SensorInfo();
info.indoortemp = Optional.ofNullable(responseMap.get("htemp")).flatMap(value -> InfoParser.parseDouble(value));
info.indoorhumidity = Optional.ofNullable(responseMap.get("hhum"))
.flatMap(value -> InfoParser.parseDouble(value));
info.outdoortemp = Optional.ofNullable(responseMap.get("otemp"))
.flatMap(value -> InfoParser.parseDouble(value));
info.compressorfrequency = Optional.ofNullable(responseMap.get("cmpfreq"))
.flatMap(value -> InfoParser.parseDouble(value));
return info;
}
}

View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2010-2020 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.HashMap;
import java.util.Map;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.daikin.internal.api.InfoParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Holds information from the basic_info call.
*
* @author Paul Smedley - Initial contribution
*
*/
@NonNullByDefault
public class AirbaseBasicInfo {
private static final Logger logger = LoggerFactory.getLogger(AirbaseBasicInfo.class);
public String mac = "";
public String ret = "";
public String ssid = "";
private AirbaseBasicInfo() {
}
public static AirbaseBasicInfo parse(String response) {
logger.debug("Parsing string: \"{}\"", response);
Map<String, String> responseMap = InfoParser.parse(response);
AirbaseBasicInfo info = new AirbaseBasicInfo();
info.mac = Optional.ofNullable(responseMap.get("mac")).orElse("");
info.ret = Optional.ofNullable(responseMap.get("ret")).orElse("");
info.ssid = Optional.ofNullable(responseMap.get("ssid")).orElse("");
return info;
}
public Map<String, String> getParamString() {
Map<String, String> params = new HashMap<>();
if (!"".equals(ssid)) {
params.put("ssid", ssid);
}
return params;
}
}

View File

@@ -0,0 +1,86 @@
/**
* Copyright (c) 2010-2020 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.HashMap;
import java.util.Map;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.daikin.internal.api.InfoParser;
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 <paul@smedley.id.au> - Mods for Daikin Airbase Units
*
*/
@NonNullByDefault
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<Double> temp = Optional.empty();
public AirbaseFanSpeed fanSpeed = AirbaseFanSpeed.LEVEL_1;
public AirbaseFanMovement fanMovement = AirbaseFanMovement.STOPPED;
/* Not supported by all units. Sets the target humidity for dehumidifying. */
public Optional<Integer> targetHumidity = Optional.empty();
private AirbaseControlInfo() {
}
public static AirbaseControlInfo parse(String response) {
LOGGER.debug("Parsing string: \"{}\"", response);
Map<String, String> responseMap = InfoParser.parse(response);
AirbaseControlInfo info = new AirbaseControlInfo();
info.ret = Optional.ofNullable(responseMap.get("ret")).orElse("");
info.power = "1".equals(responseMap.get("pow"));
info.mode = Optional.ofNullable(responseMap.get("mode")).flatMap(value -> InfoParser.parseInt(value))
.map(value -> AirbaseMode.fromValue(value)).orElse(AirbaseMode.AUTO);
info.temp = Optional.ofNullable(responseMap.get("stemp")).flatMap(value -> InfoParser.parseDouble(value));
int f_rate = Optional.ofNullable(responseMap.get("f_rate")).flatMap(value -> InfoParser.parseInt(value))
.orElse(1);
boolean f_auto = "1".equals(responseMap.getOrDefault("f_auto", "0"));
boolean f_airside = "1".equals(responseMap.getOrDefault("f_airside", "0"));
info.fanSpeed = AirbaseFanSpeed.fromValue(f_rate, f_auto, f_airside);
info.fanMovement = Optional.ofNullable(responseMap.get("f_dir")).flatMap(value -> InfoParser.parseInt(value))
.map(value -> AirbaseFanMovement.fromValue(value)).orElse(AirbaseFanMovement.STOPPED);
info.targetHumidity = Optional.ofNullable(responseMap.get("shum")).flatMap(value -> InfoParser.parseInt(value));
return info;
}
public Map<String, String> getParamString() {
Map<String, String> params = new HashMap<>();
params.put("pow", power ? "1" : "0");
params.put("mode", Integer.toString(mode.getValue()));
params.put("f_rate", Integer.toString(fanSpeed.getLevel()));
params.put("f_auto", fanSpeed.getAuto() ? "1" : "0");
params.put("f_airside", fanSpeed.getAirside() ? "1" : "0");
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;
}
}

View File

@@ -0,0 +1,190 @@
/**
* Copyright (c) 2010-2020 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.eclipse.jdt.annotation.NonNullByDefault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Container class for enums related to Daikin Airbase A/C systems
*
* @author Tim Waterhouse <tim@timwaterhouse.com> - Initial contribution
* @author Paul Smedley <paul@smedley.id.au> - Mods for Daikin Airbase Units
*
*/
@NonNullByDefault
public class AirbaseEnums {
public enum AirbaseMode {
COLD(2, "Cooling"),
HEAT(1, "Heating"),
FAN(0, "Fan"),
DRY(7, "Dehumidifier"),
AUTO(3, "Auto");
private static final Logger LOGGER = LoggerFactory.getLogger(AirbaseMode.class);
private final int value;
private final String label;
AirbaseMode(int value, String label) {
this.value = value;
this.label = label;
}
public int getValue() {
return value;
}
public String getLabel() {
return label;
}
public static AirbaseMode fromValue(int value) {
for (AirbaseMode m : AirbaseMode.values()) {
if (m.getValue() == value) {
return m;
}
}
LOGGER.debug("Unexpected Mode value of \"{}\"", value);
return AUTO;
}
}
public enum AirbaseFanSpeed {
// level,f_auto,f_airside
AUTO(0, false, false),
LEVEL_1(1, false, false),
LEVEL_2(2, false, false),
LEVEL_3(3, false, false),
LEVEL_4(4, false, false),
LEVEL_5(5, false, false),
AUTO_LEVEL_1(1, true, false),
AUTO_LEVEL_2(2, true, false),
AUTO_LEVEL_3(3, true, false),
AUTO_LEVEL_4(4, true, false),
AUTO_LEVEL_5(5, true, false),
AIRSIDE(1, false, true);
private static final Logger LOGGER = LoggerFactory.getLogger(AirbaseFanSpeed.class);
private final int level;
private final boolean auto;
private final boolean airside;
AirbaseFanSpeed(int level, boolean auto, boolean airside) {
this.level = level;
this.auto = auto;
this.airside = airside;
}
public int getLevel() {
return level;
}
public boolean getAuto() {
return auto;
}
public boolean getAirside() {
return airside;
}
public String getLabel() {
if (airside) {
return "Airside";
}
if (level == 0) {
return "Auto";
}
String label = "";
if (auto) {
label = "Auto ";
}
return label + "Level " + Integer.toString(level);
}
public static AirbaseFanSpeed fromValue(int rate, boolean auto, boolean airside) { // convert from f_rate,
// f_auto, f_airside
if (airside) {
return AIRSIDE;
}
if (rate == 0) {
return AirbaseFanSpeed.AUTO;
}
for (AirbaseFanSpeed m : AirbaseFanSpeed.values()) {
if (m.getLevel() == rate && m.getAuto() == auto && m.getAirside() == airside) {
return m;
}
}
LOGGER.debug("Unexpected FanSpeed value from rate={}, auto={}, airside={}", rate, auto ? 1 : 0,
airside ? 1 : 0);
return LEVEL_1;
}
}
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);
return STOPPED;
}
}
public enum AirbaseFeature {
ZONE("en_zone"),
FILTER_SIGN("en_filter_sign"),
TEMP_SETTING("en_temp_setting"),
FRATE("en_frate"),
DIR("en_dir"),
RTEMP_A("en_rtemp_a"),
SPMODE("en_spmode"),
MOMPOW("en_mompow"),
PATROL("en_patrol"),
AIRSIDE("en_airside"),
QUICK_TIMER("en_quick_timer"),
AUTO("en_auto"),
DRY("en_dry"),
FRATE_AUTO("en_frate_auto");
private static final Logger LOGGER = LoggerFactory.getLogger(AirbaseFeature.class);
private final String value;
AirbaseFeature(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) 2010-2020 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.EnumSet;
import java.util.Map;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.daikin.internal.api.InfoParser;
import org.openhab.binding.daikin.internal.api.airbase.AirbaseEnums.AirbaseFeature;
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
*
*/
@NonNullByDefault
public class AirbaseModelInfo {
private static final Logger LOGGER = LoggerFactory.getLogger(AirbaseModelInfo.class);
public String ret = "";
public int zonespresent;
public int commonzone;
public int frate_steps; // fan rate steps
public EnumSet<AirbaseFeature> features;
private AirbaseModelInfo() {
features = EnumSet.noneOf(AirbaseFeature.class);
}
public static AirbaseModelInfo parse(String response) {
LOGGER.debug("Parsing string: \"{}\"", response);
Map<String, String> responseMap = InfoParser.parse(response);
AirbaseModelInfo info = new AirbaseModelInfo();
info.ret = Optional.ofNullable(responseMap.get("ret")).orElse("");
info.zonespresent = Optional.ofNullable(responseMap.get("en_zone")).flatMap(value -> InfoParser.parseInt(value))
.orElse(0);
info.commonzone = Optional.ofNullable(responseMap.get("en_common_zone"))
.flatMap(value -> InfoParser.parseInt(value)).orElse(0);
info.frate_steps = Optional.ofNullable(responseMap.get("frate_steps"))
.flatMap(value -> InfoParser.parseInt(value)).orElse(1);
for (AirbaseFeature f : AirbaseFeature.values()) {
if ("1".equals(responseMap.get(f.getValue()))) {
info.features.add(f);
}
}
return info;
}
}

View File

@@ -0,0 +1,67 @@
/**
* Copyright (c) 2010-2020 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.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.daikin.internal.api.InfoParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Holds information from the basic_info call.
*
* @author Paul Smedley - Initial contribution
*
*/
@NonNullByDefault
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<String, String> responseMap = InfoParser.parse(response);
AirbaseZoneInfo info = new AirbaseZoneInfo();
info.zonenames = Optional.ofNullable(responseMap.get("zone_name")).orElse("");
String zoneinfo = Optional.ofNullable(responseMap.get("zone_onoff")).orElse("");
String[] Zones = zoneinfo.split("%3b");
for (int i = 1; i < 9; i++)
info.zone[i] = "1".equals(Zones[i - 1]);
return info;
}
public Map<String, String> getParamString() {
Map<String, String> params = new LinkedHashMap<>();
String onoffstring = IntStream.range(1, zone.length).mapToObj(idx -> zone[idx] ? "1" : "0")
.collect(Collectors.joining("%3b"));
params.put("zone_name", zonenames);
params.put("zone_onoff", onoffstring);
return params;
}
}

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2010-2020 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.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Holds configuration data for a Daikin air conditioning unit.
*
* @author Tim Waterhouse - Initial contribution
* @author Jimmy Tanagra - Add secure, uuid
*
*/
@NonNullByDefault
public class DaikinConfiguration {
public static final String HOST = "host";
public static final String SECURE = "secure";
public static final String UUID = "uuid";
public static final String KEY = "key";
public @Nullable String host;
public @Nullable Boolean secure;
public @Nullable String uuid;
public @Nullable String key;
public long refresh;
}

View File

@@ -0,0 +1,210 @@
/**
* Copyright (c) 2010-2020 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.discovery;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.daikin.internal.DaikinBindingConstants;
import org.openhab.binding.daikin.internal.DaikinCommunicationForbiddenException;
import org.openhab.binding.daikin.internal.DaikinHttpClientFactory;
import org.openhab.binding.daikin.internal.DaikinWebTargets;
import org.openhab.binding.daikin.internal.api.InfoParser;
import org.openhab.binding.daikin.internal.config.DaikinConfiguration;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.net.NetUtil;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Discovery service for Daikin AC units.
*
* @author Tim Waterhouse <tim@timwaterhouse.com> - Initial contribution
* @author Paul Smedley <paul@smedley.id.au> - Modifications to support Airbase Controllers
*
*/
@Component(service = DiscoveryService.class)
@NonNullByDefault
public class DaikinACUnitDiscoveryService extends AbstractDiscoveryService {
private static final String UDP_PACKET_CONTENTS = "DAIKIN_UDP/common/basic_info";
private static final int REMOTE_UDP_PORT = 30050;
private Logger logger = LoggerFactory.getLogger(DaikinACUnitDiscoveryService.class);
private @Nullable HttpClient httpClient;
private final Runnable scanner;
private @Nullable ScheduledFuture<?> backgroundFuture;
public DaikinACUnitDiscoveryService() {
super(Collections.singleton(DaikinBindingConstants.THING_TYPE_AC_UNIT), 600, true);
scanner = createScanner();
}
@Override
protected void startScan() {
scheduler.execute(scanner);
}
@Override
protected void startBackgroundDiscovery() {
logger.debug("Starting background discovery");
if (backgroundFuture != null && !backgroundFuture.isDone()) {
backgroundFuture.cancel(true);
backgroundFuture = null;
}
backgroundFuture = scheduler.scheduleWithFixedDelay(scanner, 0, 60, TimeUnit.SECONDS);
}
@Override
protected void stopBackgroundDiscovery() {
if (backgroundFuture != null && !backgroundFuture.isDone()) {
backgroundFuture.cancel(true);
backgroundFuture = null;
}
super.stopBackgroundDiscovery();
}
private Runnable createScanner() {
return () -> {
long timestampOfLastScan = getTimestampOfLastScan();
for (InetAddress broadcastAddress : getBroadcastAddresses()) {
logger.debug("Starting broadcast for {}", broadcastAddress.toString());
try (DatagramSocket socket = new DatagramSocket()) {
socket.setBroadcast(true);
socket.setReuseAddress(true);
byte[] packetContents = UDP_PACKET_CONTENTS.getBytes(StandardCharsets.UTF_8);
DatagramPacket packet = new DatagramPacket(packetContents, packetContents.length, broadcastAddress,
REMOTE_UDP_PORT);
// Send before listening in case the port isn't bound until here.
socket.send(packet);
// receivePacketAndDiscover will return false if no packet is received after 1 second
while (receivePacketAndDiscover(socket)) {
}
} catch (Exception e) {
// Nothing to do here - the host couldn't be found, likely because it doesn't exist
}
}
removeOlderResults(timestampOfLastScan);
};
}
private boolean receivePacketAndDiscover(DatagramSocket socket) {
try {
byte[] buffer = new byte[512];
DatagramPacket incomingPacket = new DatagramPacket(buffer, buffer.length);
socket.setSoTimeout(1000 /* one second */);
socket.receive(incomingPacket);
String host = incomingPacket.getAddress().toString().substring(1);
String data = new String(incomingPacket.getData(), 0, incomingPacket.getLength(), "US-ASCII");
logger.debug("Received packet from {}: {}", host, data);
Map<String, String> parsedData = InfoParser.parse(data);
Boolean secure = "1".equals(parsedData.get("en_secure"));
String thingId = Optional.ofNullable(parsedData.get("ssid")).orElse(host.replace(".", "_"));
String mac = Optional.ofNullable(parsedData.get("mac")).orElse("");
String uuid = mac.isEmpty() ? UUID.randomUUID().toString()
: UUID.nameUUIDFromBytes(mac.getBytes()).toString();
DaikinWebTargets webTargets = new DaikinWebTargets(httpClient, host, secure, null);
boolean found = false;
// look for Daikin controller
try {
found = "OK".equals(webTargets.getBasicInfo().ret);
} catch (DaikinCommunicationForbiddenException e) {
// At this point, we don't have the adapter's key nor a uuid
// so we're getting a Forbidden error
// let's discover it and let the user configure the Key
found = true;
}
if (found) {
ThingUID thingUID = new ThingUID(DaikinBindingConstants.THING_TYPE_AC_UNIT, thingId);
DiscoveryResultBuilder resultBuilder = DiscoveryResultBuilder.create(thingUID)
.withProperty(DaikinConfiguration.HOST, host).withLabel("Daikin AC Unit (" + host + ")")
.withProperty(DaikinConfiguration.SECURE, secure)
.withRepresentationProperty(DaikinConfiguration.HOST);
if (secure) {
resultBuilder = resultBuilder.withProperty(DaikinConfiguration.UUID, uuid);
}
DiscoveryResult result = resultBuilder.build();
logger.debug("Successfully discovered host {}", host);
thingDiscovered(result);
return true;
}
// look for Daikin Airbase controller
if ("OK".equals(webTargets.getAirbaseBasicInfo().ret)) {
ThingUID thingUID = new ThingUID(DaikinBindingConstants.THING_TYPE_AIRBASE_AC_UNIT, thingId);
DiscoveryResult result = DiscoveryResultBuilder.create(thingUID)
.withProperty(DaikinConfiguration.HOST, host).withLabel("Daikin Airbase AC Unit (" + host + ")")
.withRepresentationProperty(DaikinConfiguration.HOST).build();
logger.debug("Successfully discovered host {}", host);
thingDiscovered(result);
return true;
}
} catch (Exception e) {
return false;
}
// Shouldn't get here unless we don't detect a controller.
// Return true to continue with the next packet, which comes from another adapter
return true;
}
private List<InetAddress> getBroadcastAddresses() {
ArrayList<InetAddress> addresses = new ArrayList<>();
for (String broadcastAddress : NetUtil.getAllBroadcastAddresses()) {
try {
addresses.add(InetAddress.getByName(broadcastAddress));
} catch (UnknownHostException e) {
logger.debug("Error broadcasting to {}", broadcastAddress, e);
}
}
return addresses;
}
@Reference
protected void setDaikinHttpClientFactory(final DaikinHttpClientFactory httpClientFactory) {
this.httpClient = httpClientFactory.getHttpClient();
}
}

View File

@@ -0,0 +1,275 @@
/**
* Copyright (c) 2010-2020 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.handler;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Optional;
import java.util.stream.IntStream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.daikin.internal.DaikinBindingConstants;
import org.openhab.binding.daikin.internal.DaikinCommunicationException;
import org.openhab.binding.daikin.internal.DaikinDynamicStateDescriptionProvider;
import org.openhab.binding.daikin.internal.DaikinWebTargets;
import org.openhab.binding.daikin.internal.api.ControlInfo;
import org.openhab.binding.daikin.internal.api.EnergyInfoYear;
import org.openhab.binding.daikin.internal.api.Enums.FanMovement;
import org.openhab.binding.daikin.internal.api.Enums.FanSpeed;
import org.openhab.binding.daikin.internal.api.Enums.HomekitMode;
import org.openhab.binding.daikin.internal.api.Enums.Mode;
import org.openhab.binding.daikin.internal.api.Enums.SpecialModeKind;
import org.openhab.binding.daikin.internal.api.SensorInfo;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SmartHomeUnits;
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.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handles communicating with a Daikin air conditioning unit.
*
* @author Tim Waterhouse - Initial Contribution
* @author Paul Smedley <paul@smedley.id.au> - Modifications to support Airbase Controllers
* @author Lukas Agethen - Added support for Energy Year reading, compressor frequency and powerful mode
*/
@NonNullByDefault
public class DaikinAcUnitHandler extends DaikinBaseHandler {
private final Logger logger = LoggerFactory.getLogger(DaikinAcUnitHandler.class);
public DaikinAcUnitHandler(Thing thing, DaikinDynamicStateDescriptionProvider stateDescriptionProvider,
@Nullable HttpClient httpClient) {
super(thing, stateDescriptionProvider, httpClient);
}
@Override
protected void pollStatus() throws IOException {
DaikinWebTargets webTargets = this.webTargets;
if (webTargets == null) {
return;
}
ControlInfo controlInfo = webTargets.getControlInfo();
updateStatus(ThingStatus.ONLINE);
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_AC_FAN_SPEED, new StringType(controlInfo.fanSpeed.name()));
updateState(DaikinBindingConstants.CHANNEL_AC_FAN_DIR, new StringType(controlInfo.fanMovement.name()));
if (!controlInfo.power) {
updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.OFF.getValue()));
} else if (controlInfo.mode == Mode.COLD) {
updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.COOL.getValue()));
} else if (controlInfo.mode == Mode.HEAT) {
updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.HEAT.getValue()));
} else if (controlInfo.mode == Mode.AUTO) {
updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.AUTO.getValue()));
}
updateState(DaikinBindingConstants.CHANNEL_AC_SPECIALMODE, new StringType(controlInfo.specialMode.name()));
if (controlInfo.specialMode.isUndefined()) {
updateState(DaikinBindingConstants.CHANNEL_AC_SPECIALMODE_POWERFUL, UnDefType.UNDEF);
} else {
updateState(DaikinBindingConstants.CHANNEL_AC_SPECIALMODE_POWERFUL,
controlInfo.specialMode.isPowerfulActive() ? OnOffType.ON : OnOffType.OFF);
}
SensorInfo sensorInfo = webTargets.getSensorInfo();
updateTemperatureChannel(DaikinBindingConstants.CHANNEL_INDOOR_TEMP, sensorInfo.indoortemp);
updateTemperatureChannel(DaikinBindingConstants.CHANNEL_OUTDOOR_TEMP, sensorInfo.outdoortemp);
if (sensorInfo.indoorhumidity.isPresent()) {
updateState(DaikinBindingConstants.CHANNEL_HUMIDITY,
new QuantityType<>(sensorInfo.indoorhumidity.get(), SmartHomeUnits.PERCENT));
} else {
updateState(DaikinBindingConstants.CHANNEL_HUMIDITY, UnDefType.UNDEF);
}
if (sensorInfo.compressorfrequency.isPresent()) {
updateState(DaikinBindingConstants.CHANNEL_CMP_FREQ,
new QuantityType<>(sensorInfo.compressorfrequency.get(), SmartHomeUnits.PERCENT));
} else {
updateState(DaikinBindingConstants.CHANNEL_CMP_FREQ, UnDefType.UNDEF);
}
try {
EnergyInfoYear energyInfoYear = webTargets.getEnergyInfoYear();
if (energyInfoYear.energyHeatingThisYear.isPresent()) {
updateEnergyYearChannel(DaikinBindingConstants.CHANNEL_ENERGY_HEATING_CURRENTYEAR_PREFIX,
energyInfoYear.energyHeatingThisYear);
}
if (energyInfoYear.energyCoolingThisYear.isPresent()) {
updateEnergyYearChannel(DaikinBindingConstants.CHANNEL_ENERGY_COOLING_CURRENTYEAR_PREFIX,
energyInfoYear.energyCoolingThisYear);
}
} catch (DaikinCommunicationException e) {
// Suppress any error if energy info is not supported.
logger.debug("getEnergyInfoYear() error: {}", e.getMessage());
}
}
@Override
protected boolean handleCommandInternal(ChannelUID channelUID, Command command)
throws DaikinCommunicationException {
switch (channelUID.getId()) {
case DaikinBindingConstants.CHANNEL_AC_FAN_DIR:
if (command instanceof StringType) {
changeFanDir(((StringType) command).toString());
return true;
}
break;
case DaikinBindingConstants.CHANNEL_AC_SPECIALMODE_POWERFUL:
if (command instanceof OnOffType) {
changeSpecialModePowerful(((OnOffType) command).equals(OnOffType.ON));
return true;
}
break;
}
return false;
}
@Override
protected void changePower(boolean power) throws DaikinCommunicationException {
DaikinWebTargets webTargets = this.webTargets;
if (webTargets == null) {
return;
}
ControlInfo info = webTargets.getControlInfo();
info.power = power;
webTargets.setControlInfo(info);
}
@Override
protected void changeSetPoint(double newTemperature) throws DaikinCommunicationException {
DaikinWebTargets webTargets = this.webTargets;
if (webTargets == null) {
return;
}
ControlInfo info = webTargets.getControlInfo();
info.temp = Optional.of(newTemperature);
webTargets.setControlInfo(info);
}
@Override
protected void changeMode(String mode) throws DaikinCommunicationException {
DaikinWebTargets webTargets = this.webTargets;
if (webTargets == null) {
return;
}
Mode newMode;
try {
newMode = Mode.valueOf(mode);
} catch (IllegalArgumentException ex) {
logger.warn("Invalid mode: {}. Valid values: {}", mode, Mode.values());
return;
}
ControlInfo info = webTargets.getControlInfo();
info.mode = newMode;
webTargets.setControlInfo(info);
}
@Override
protected void changeFanSpeed(String fanSpeed) throws DaikinCommunicationException {
DaikinWebTargets webTargets = this.webTargets;
if (webTargets == null) {
return;
}
FanSpeed newSpeed;
try {
newSpeed = FanSpeed.valueOf(fanSpeed);
} catch (IllegalArgumentException ex) {
logger.warn("Invalid fan speed: {}. Valid values: {}", fanSpeed, FanSpeed.values());
return;
}
ControlInfo info = webTargets.getControlInfo();
info.fanSpeed = newSpeed;
webTargets.setControlInfo(info);
}
protected void changeFanDir(String fanDir) throws DaikinCommunicationException {
DaikinWebTargets webTargets = this.webTargets;
if (webTargets == null) {
return;
}
FanMovement newMovement;
try {
newMovement = FanMovement.valueOf(fanDir);
} catch (IllegalArgumentException ex) {
logger.warn("Invalid fan direction: {}. Valid values: {}", fanDir, FanMovement.values());
return;
}
ControlInfo info = webTargets.getControlInfo();
info.fanMovement = newMovement;
webTargets.setControlInfo(info);
}
/**
*
* @param powerfulMode
* @return Is change successful
* @throws DaikinCommunicationException
*/
protected boolean changeSpecialModePowerful(boolean powerfulMode) throws DaikinCommunicationException {
DaikinWebTargets webTargets = this.webTargets;
if (webTargets == null) {
return false;
}
return webTargets.setSpecialMode(SpecialModeKind.POWERFUL, powerfulMode);
}
/**
* Updates energy year channels. Values are provided in hundreds of Watt
*
* @param channelPrefix
* @param maybePower
*/
protected void updateEnergyYearChannel(String channelPrefix, Optional<Integer[]> maybePower) {
IntStream.range(1, 13)
.forEach(i -> updateState(
String.format(DaikinBindingConstants.CHANNEL_ENERGY_STRING_FORMAT, channelPrefix, i),
maybePower.<State> map(t -> new QuantityType<>(BigDecimal.valueOf(t[i - 1].longValue(), 1),
SmartHomeUnits.KILOWATT_HOUR)).orElse(UnDefType.UNDEF))
);
}
@Override
protected void registerUuid(@Nullable String key) {
if (key == null) {
return;
}
try {
DaikinWebTargets webTargets = this.webTargets;
if (webTargets == null) {
return;
}
webTargets.registerUuid(key);
} catch (Exception e) {
// suppress exceptions
logger.debug("registerUuid({}) error: {}", key, e.getMessage());
}
}
}

View File

@@ -0,0 +1,240 @@
/**
* Copyright (c) 2010-2020 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.handler;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.stream.IntStream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.daikin.internal.DaikinBindingConstants;
import org.openhab.binding.daikin.internal.DaikinCommunicationException;
import org.openhab.binding.daikin.internal.DaikinDynamicStateDescriptionProvider;
import org.openhab.binding.daikin.internal.api.Enums.HomekitMode;
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.AirbaseEnums.AirbaseFanSpeed;
import org.openhab.binding.daikin.internal.api.airbase.AirbaseEnums.AirbaseFeature;
import org.openhab.binding.daikin.internal.api.airbase.AirbaseEnums.AirbaseMode;
import org.openhab.binding.daikin.internal.api.airbase.AirbaseModelInfo;
import org.openhab.binding.daikin.internal.api.airbase.AirbaseZoneInfo;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
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.StateOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handles communicating with a Daikin Airbase wifi adapter.
*
* @author Tim Waterhouse - Initial Contribution
* @author Paul Smedley <paul@smedley.id.au> - Modifications to support Airbase Controllers
* @author Jimmy Tanagra - Support Airside and auto fan levels, DynamicStateDescription
*
*/
@NonNullByDefault
public class DaikinAirbaseUnitHandler extends DaikinBaseHandler {
private final Logger logger = LoggerFactory.getLogger(DaikinAirbaseUnitHandler.class);
private @Nullable AirbaseModelInfo airbaseModelInfo;
public DaikinAirbaseUnitHandler(Thing thing, DaikinDynamicStateDescriptionProvider stateDescriptionProvider,
@Nullable HttpClient httpClient) {
super(thing, stateDescriptionProvider, httpClient);
}
@Override
protected boolean handleCommandInternal(ChannelUID channelUID, Command command)
throws DaikinCommunicationException {
if (channelUID.getId().startsWith(DaikinBindingConstants.CHANNEL_AIRBASE_AC_ZONE)) {
int zoneNumber = Integer.parseInt(channelUID.getId().substring(4));
if (command instanceof OnOffType) {
changeZone(zoneNumber, command == OnOffType.ON);
return true;
}
}
return false;
}
@Override
protected void pollStatus() throws IOException {
AirbaseControlInfo controlInfo = webTargets.getAirbaseControlInfo();
updateStatus(ThingStatus.ONLINE);
if (airbaseModelInfo == null || !"OK".equals(airbaseModelInfo.ret)) {
airbaseModelInfo = webTargets.getAirbaseModelInfo();
updateChannelStateDescriptions();
}
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()));
if (!controlInfo.power) {
updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.OFF.getValue()));
} else if (controlInfo.mode == AirbaseMode.COLD) {
updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.COOL.getValue()));
} else if (controlInfo.mode == AirbaseMode.HEAT) {
updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.HEAT.getValue()));
} else if (controlInfo.mode == AirbaseMode.AUTO) {
updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.AUTO.getValue()));
}
}
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])));
}
}
@Override
protected void changePower(boolean power) throws DaikinCommunicationException {
AirbaseControlInfo info = webTargets.getAirbaseControlInfo();
info.power = power;
webTargets.setAirbaseControlInfo(info);
}
@Override
protected void changeSetPoint(double newTemperature) throws DaikinCommunicationException {
AirbaseControlInfo info = webTargets.getAirbaseControlInfo();
info.temp = Optional.of(newTemperature);
webTargets.setAirbaseControlInfo(info);
}
@Override
protected void changeMode(String mode) throws DaikinCommunicationException {
AirbaseMode newMode;
try {
newMode = AirbaseMode.valueOf(mode);
} catch (IllegalArgumentException ex) {
logger.warn("Invalid mode: {}. Valid values: {}", mode, AirbaseMode.values());
return;
}
if (airbaseModelInfo != null) {
if ((newMode == AirbaseMode.AUTO && !airbaseModelInfo.features.contains(AirbaseFeature.AUTO))
|| (newMode == AirbaseMode.DRY && !airbaseModelInfo.features.contains(AirbaseFeature.DRY))) {
logger.warn("{} mode is not supported by your controller", mode);
return;
}
}
AirbaseControlInfo info = webTargets.getAirbaseControlInfo();
info.mode = newMode;
webTargets.setAirbaseControlInfo(info);
}
@Override
protected void changeFanSpeed(String speed) throws DaikinCommunicationException {
AirbaseFanSpeed newFanSpeed;
try {
newFanSpeed = AirbaseFanSpeed.valueOf(speed);
} catch (IllegalArgumentException ex) {
logger.warn("Invalid fan speed: {}. Valid values: {}", speed, AirbaseFanSpeed.values());
return;
}
if (airbaseModelInfo != null) {
if (EnumSet.range(AirbaseFanSpeed.AUTO_LEVEL_1, AirbaseFanSpeed.AUTO_LEVEL_5).contains(newFanSpeed)
&& !airbaseModelInfo.features.contains(AirbaseFeature.FRATE_AUTO)) {
logger.warn("Fan AUTO_LEVEL_X is not supported by your controller");
return;
}
if (newFanSpeed == AirbaseFanSpeed.AIRSIDE && !airbaseModelInfo.features.contains(AirbaseFeature.AIRSIDE)) {
logger.warn("Airside is not supported by your controller");
return;
}
}
AirbaseControlInfo info = webTargets.getAirbaseControlInfo();
info.fanSpeed = newFanSpeed;
webTargets.setAirbaseControlInfo(info);
}
protected void changeZone(int zone, boolean command) throws DaikinCommunicationException {
if (zone <= 0 || zone > airbaseModelInfo.zonespresent) {
logger.warn("The given zone number ({}) is outside the number of zones supported by the controller ({})",
zone, airbaseModelInfo.zonespresent);
return;
}
AirbaseZoneInfo zoneInfo = webTargets.getAirbaseZoneInfo();
long count = IntStream.range(0, zoneInfo.zone.length).filter(idx -> zoneInfo.zone[idx]).count()
+ airbaseModelInfo.commonzone;
logger.debug("Number of open zones: \"{}\"", count);
if (count >= 1) {
zoneInfo.zone[zone] = command;
webTargets.setAirbaseZoneInfo(zoneInfo);
}
}
@Override
protected void registerUuid(@Nullable String key) {
// not implemented. There is currently no known Airbase adapter that requires uuid authentication
}
protected void updateChannelStateDescriptions() {
updateAirbaseFanSpeedChannelStateDescription();
updateAirbaseModeChannelStateDescription();
}
protected void updateAirbaseFanSpeedChannelStateDescription() {
List<StateOption> options = new ArrayList<>();
options.add(new StateOption(AirbaseFanSpeed.AUTO.name(), AirbaseFanSpeed.AUTO.getLabel()));
if (airbaseModelInfo.features.contains(AirbaseFeature.AIRSIDE)) {
options.add(new StateOption(AirbaseFanSpeed.AIRSIDE.name(), AirbaseFanSpeed.AIRSIDE.getLabel()));
}
for (AirbaseFanSpeed f : EnumSet.range(AirbaseFanSpeed.LEVEL_1, AirbaseFanSpeed.LEVEL_5)) {
options.add(new StateOption(f.name(), f.getLabel()));
}
if (airbaseModelInfo.features.contains(AirbaseFeature.FRATE_AUTO)) {
for (AirbaseFanSpeed f : EnumSet.range(AirbaseFanSpeed.AUTO_LEVEL_1, AirbaseFanSpeed.AUTO_LEVEL_5)) {
options.add(new StateOption(f.name(), f.getLabel()));
}
}
stateDescriptionProvider.setStateOptions(
new ChannelUID(thing.getUID(), DaikinBindingConstants.CHANNEL_AIRBASE_AC_FAN_SPEED), options);
}
protected void updateAirbaseModeChannelStateDescription() {
List<StateOption> options = new ArrayList<>();
if (airbaseModelInfo.features.contains(AirbaseFeature.AUTO)) {
options.add(new StateOption(AirbaseMode.AUTO.name(), AirbaseMode.AUTO.getLabel()));
}
for (AirbaseMode f : EnumSet.complementOf(EnumSet.of(AirbaseMode.AUTO, AirbaseMode.DRY))) {
options.add(new StateOption(f.name(), f.getLabel()));
}
if (airbaseModelInfo.features.contains(AirbaseFeature.DRY)) {
options.add(new StateOption(AirbaseMode.DRY.name(), AirbaseMode.DRY.getLabel()));
}
stateDescriptionProvider.setStateOptions(new ChannelUID(thing.getUID(), DaikinBindingConstants.CHANNEL_AC_MODE),
options);
}
}

View File

@@ -0,0 +1,244 @@
/**
* Copyright (c) 2010-2020 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.handler;
import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.measure.quantity.Temperature;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.daikin.internal.DaikinBindingConstants;
import org.openhab.binding.daikin.internal.DaikinCommunicationException;
import org.openhab.binding.daikin.internal.DaikinCommunicationForbiddenException;
import org.openhab.binding.daikin.internal.DaikinDynamicStateDescriptionProvider;
import org.openhab.binding.daikin.internal.DaikinWebTargets;
import org.openhab.binding.daikin.internal.api.Enums.HomekitMode;
import org.openhab.binding.daikin.internal.config.DaikinConfiguration;
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.types.StringType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base class that handles common tasks with a Daikin air conditioning unit.
*
* @author Tim Waterhouse - Initial Contribution
* @author Paul Smedley <paul@smedley.id.au> - Modifications to support Airbase Controllers
* @author Jimmy Tanagra - Split handler classes, support Airside and DynamicStateDescription
*
*/
@NonNullByDefault
public abstract class DaikinBaseHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(DaikinBaseHandler.class);
private final @Nullable HttpClient httpClient;
private long refreshInterval;
protected @Nullable DaikinWebTargets webTargets;
private @Nullable ScheduledFuture<?> pollFuture;
protected final DaikinDynamicStateDescriptionProvider stateDescriptionProvider;
protected @Nullable DaikinConfiguration config;
private boolean uuidRegistrationAttempted = false;
// Abstract methods to be overridden by specific Daikin implementation class
protected abstract void pollStatus() throws IOException;
protected abstract void changePower(boolean power) throws DaikinCommunicationException;
protected abstract void changeSetPoint(double newTemperature) throws DaikinCommunicationException;
protected abstract void changeMode(String mode) throws DaikinCommunicationException;
protected abstract void changeFanSpeed(String fanSpeed) throws DaikinCommunicationException;
// Power, Temp, Fan and Mode are handled in this base class. Override this to handle additional channels.
protected abstract boolean handleCommandInternal(ChannelUID channelUID, Command command)
throws DaikinCommunicationException;
protected abstract void registerUuid(@Nullable String key);
public DaikinBaseHandler(Thing thing, DaikinDynamicStateDescriptionProvider stateDescriptionProvider,
@Nullable HttpClient httpClient) {
super(thing);
this.stateDescriptionProvider = stateDescriptionProvider;
this.httpClient = httpClient;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
try {
if (handleCommandInternal(channelUID, command)) {
return;
}
switch (channelUID.getId()) {
case DaikinBindingConstants.CHANNEL_AC_POWER:
if (command instanceof OnOffType) {
changePower(((OnOffType) command).equals(OnOffType.ON));
return;
}
break;
case DaikinBindingConstants.CHANNEL_AC_TEMP:
if (changeSetPoint(command)) {
return;
}
break;
case DaikinBindingConstants.CHANNEL_AIRBASE_AC_FAN_SPEED:
case DaikinBindingConstants.CHANNEL_AC_FAN_SPEED:
if (command instanceof StringType) {
changeFanSpeed(((StringType) command).toString());
return;
}
break;
case DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE:
if (command instanceof StringType) {
changeHomekitMode(command.toString());
return;
}
break;
case DaikinBindingConstants.CHANNEL_AC_MODE:
if (command instanceof StringType) {
changeMode(((StringType) command).toString());
return;
}
break;
}
logger.debug("Received command ({}) of wrong type for thing '{}' on channel {}", command,
thing.getUID().getAsString(), channelUID.getId());
} catch (DaikinCommunicationException ex) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ex.getMessage());
}
}
@Override
public void initialize() {
logger.debug("Initializing Daikin AC Unit");
config = getConfigAs(DaikinConfiguration.class);
if (config.host == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Host address must be set");
} else {
if (config.uuid != null) {
config.uuid = config.uuid.replaceAll("\\s|-", "");
}
webTargets = new DaikinWebTargets(httpClient, config.host, config.secure, config.uuid);
refreshInterval = config.refresh;
schedulePoll();
}
}
@Override
public void handleRemoval() {
stopPoll();
super.handleRemoval();
}
@Override
public void dispose() {
stopPoll();
super.dispose();
}
protected void schedulePoll() {
if (pollFuture != null) {
pollFuture.cancel(false);
}
logger.debug("Scheduling poll for 1s out, then every {} s", refreshInterval);
pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 1, refreshInterval, TimeUnit.SECONDS);
}
protected synchronized void stopPoll() {
if (pollFuture != null && !pollFuture.isCancelled()) {
pollFuture.cancel(true);
pollFuture = null;
}
}
private synchronized void poll() {
try {
logger.debug("Polling for state");
pollStatus();
} catch (DaikinCommunicationForbiddenException e) {
if (!uuidRegistrationAttempted && config.key != null && config.uuid != null) {
logger.debug("poll: Attempting to register uuid {} with key {}", config.uuid, config.key);
registerUuid(config.key);
uuidRegistrationAttempted = true;
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Access denied. Check uuid/key.");
logger.warn("{} access denied by adapter. Check uuid/key.", thing.getUID());
}
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} catch (RuntimeException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
protected void updateTemperatureChannel(String channel, Optional<Double> maybeTemperature) {
updateState(channel,
maybeTemperature.<State> map(t -> new QuantityType<>(t, SIUnits.CELSIUS)).orElse(UnDefType.UNDEF));
}
/**
* @return true if the command was of an expected type, false otherwise
*/
private boolean changeSetPoint(Command command) throws DaikinCommunicationException {
double newTemperature;
if (command instanceof DecimalType) {
newTemperature = ((DecimalType) command).doubleValue();
} else if (command instanceof QuantityType) {
newTemperature = ((QuantityType<Temperature>) command).toUnit(SIUnits.CELSIUS).doubleValue();
} else {
return false;
}
// Only half degree increments are allowed, all others are silently rejected by the A/C units
newTemperature = Math.round(newTemperature * 2) / 2.0;
changeSetPoint(newTemperature);
return true;
}
private void changeHomekitMode(String homekitmode) throws DaikinCommunicationException {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (HomekitMode.OFF.getValue().equals(homekitmode)) {
changePower(false);
} else {
changePower(true);
if (HomekitMode.AUTO.getValue().equals(homekitmode)) {
changeMode("AUTO");
} else if (HomekitMode.HEAT.getValue().equals(homekitmode)) {
changeMode("HEAT");
} else if (HomekitMode.COOL.getValue().equals(homekitmode)) {
changeMode("COLD");
}
}
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="daikin" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>Daikin Binding</name>
<description>This is the binding for Daikin A/C units.</description>
<author>Tim Waterhouse</author>
</binding:binding>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:daikin:config">
<parameter name="host" type="text" required="true">
<label>Host</label>
<description>The Host address of the Daikin AC unit.</description>
<context>network-address</context>
</parameter>
<parameter name="secure" type="boolean" required="false">
<label>Secure/HTTPS</label>
<description>Whether to access using https (default:false).</description>
</parameter>
<parameter name="uuid" type="text" required="false">
<label>UUID</label>
<description>A unique UUID for authentication if required.</description>
</parameter>
<parameter name="key" type="text" required="false">
<label>Key</label>
<description>The key obtained from the Daikin adapter.</description>
</parameter>
<parameter name="refresh" type="integer" required="false" unit="s">
<label>Refresh Interval</label>
<description>Time between fetches of the AC unit state.</description>
<default>60</default>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -0,0 +1,397 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="daikin"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="ac_unit">
<label>Daikin AC Unit</label>
<description>Daikin AC Unit</description>
<channels>
<channel id="power" typeId="acunit-power"/>
<channel id="settemp" typeId="acunit-settemp"/>
<channel id="indoortemp" typeId="acunit-indoortemp"/>
<channel id="outdoortemp" typeId="acunit-outdoortemp"/>
<channel id="humidity" typeId="acunit-humidity"/>
<channel id="mode" typeId="acunit-mode"/>
<channel id="homekitmode" typeId="acunit-homekitmode"/>
<channel id="fanspeed" typeId="acunit-fan"/>
<channel id="fandir" typeId="acunit-fandir"/>
<channel id="cmpfrequency" typeId="acunit-cmpfrequency"></channel>
<channel id="specialmode" typeId="acunit-specialmode"></channel>
<channel id="specialmode-powerful" typeId="acunit-specialmode-powerful"></channel>
<channel id="energyheatingcurrentyear-1" typeId="acunit-energyheatingcurrentyear-1"></channel>
<channel id="energyheatingcurrentyear-2" typeId="acunit-energyheatingcurrentyear-2"></channel>
<channel id="energyheatingcurrentyear-3" typeId="acunit-energyheatingcurrentyear-3"></channel>
<channel id="energyheatingcurrentyear-4" typeId="acunit-energyheatingcurrentyear-4"></channel>
<channel id="energyheatingcurrentyear-5" typeId="acunit-energyheatingcurrentyear-5"></channel>
<channel id="energyheatingcurrentyear-6" typeId="acunit-energyheatingcurrentyear-6"></channel>
<channel id="energyheatingcurrentyear-7" typeId="acunit-energyheatingcurrentyear-7"></channel>
<channel id="energyheatingcurrentyear-8" typeId="acunit-energyheatingcurrentyear-8"></channel>
<channel id="energyheatingcurrentyear-9" typeId="acunit-energyheatingcurrentyear-9"></channel>
<channel id="energyheatingcurrentyear-10" typeId="acunit-energyheatingcurrentyear-10"></channel>
<channel id="energyheatingcurrentyear-11" typeId="acunit-energyheatingcurrentyear-11"></channel>
<channel id="energyheatingcurrentyear-12" typeId="acunit-energyheatingcurrentyear-12"></channel>
<channel id="energycoolingcurrentyear-1" typeId="acunit-energycoolingcurrentyear-1"></channel>
<channel id="energycoolingcurrentyear-2" typeId="acunit-energycoolingcurrentyear-2"></channel>
<channel id="energycoolingcurrentyear-3" typeId="acunit-energycoolingcurrentyear-3"></channel>
<channel id="energycoolingcurrentyear-4" typeId="acunit-energycoolingcurrentyear-4"></channel>
<channel id="energycoolingcurrentyear-5" typeId="acunit-energycoolingcurrentyear-5"></channel>
<channel id="energycoolingcurrentyear-6" typeId="acunit-energycoolingcurrentyear-6"></channel>
<channel id="energycoolingcurrentyear-7" typeId="acunit-energycoolingcurrentyear-7"></channel>
<channel id="energycoolingcurrentyear-8" typeId="acunit-energycoolingcurrentyear-8"></channel>
<channel id="energycoolingcurrentyear-9" typeId="acunit-energycoolingcurrentyear-9"></channel>
<channel id="energycoolingcurrentyear-10" typeId="acunit-energycoolingcurrentyear-10"></channel>
<channel id="energycoolingcurrentyear-11" typeId="acunit-energycoolingcurrentyear-11"></channel>
<channel id="energycoolingcurrentyear-12" typeId="acunit-energycoolingcurrentyear-12"></channel>
</channels>
<representation-property>host</representation-property>
<config-description-ref uri="thing-type:daikin:config"/>
</thing-type>
<thing-type id="airbase_ac_unit">
<label>Daikin Airbase AC Unit</label>
<description>Daikin Airbase AC Unit</description>
<channels>
<channel id="power" typeId="acunit-power"/>
<channel id="settemp" typeId="acunit-settemp"/>
<channel id="indoortemp" typeId="acunit-indoortemp"/>
<channel id="outdoortemp" typeId="acunit-outdoortemp"/>
<channel id="mode" typeId="acunit-mode"/>
<channel id="homekitmode" typeId="acunit-homekitmode"/>
<channel id="airbasefanspeed" typeId="airbase-acunit-fan"/>
<channel id="zone1" typeId="airbase-acunit-zone1"/>
<channel id="zone2" typeId="airbase-acunit-zone2"/>
<channel id="zone3" typeId="airbase-acunit-zone3"/>
<channel id="zone4" typeId="airbase-acunit-zone4"/>
<channel id="zone5" typeId="airbase-acunit-zone5"/>
<channel id="zone6" typeId="airbase-acunit-zone6"/>
<channel id="zone7" typeId="airbase-acunit-zone7"/>
<channel id="zone8" typeId="airbase-acunit-zone8"/>
</channels>
<representation-property>host</representation-property>
<config-description-ref uri="thing-type:daikin:config"/>
</thing-type>
<channel-type id="acunit-power">
<item-type>Switch</item-type>
<label>Power</label>
<description>Power for the AC unit</description>
</channel-type>
<channel-type id="acunit-settemp">
<item-type>Number:Temperature</item-type>
<label>Set Point</label>
<description>The set point temperature</description>
<category>Temperature</category>
<state pattern="%.1f %unit%" step="0.5"/>
</channel-type>
<channel-type id="acunit-indoortemp">
<item-type>Number:Temperature</item-type>
<label>Indoor Temperature</label>
<description>The indoor temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%" step="0.5"/>
</channel-type>
<channel-type id="acunit-outdoortemp">
<item-type>Number:Temperature</item-type>
<label>Outdoor Temperature</label>
<description>The outdoor temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%" step="0.5"/>
</channel-type>
<channel-type id="acunit-humidity" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Indoor Humidity</label>
<description>The indoor humidity as measured by the A/C unit</description>
<state readOnly="true" pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="acunit-mode">
<item-type>String</item-type>
<label>Mode</label>
<description>Current mode of the AC unit</description>
<state>
<options>
<option value="AUTO">auto</option>
<option value="DEHUMIDIFIER">dehumidifier</option>
<option value="COLD">cooling</option>
<option value="HEAT">heating</option>
<option value="FAN">fan</option>
</options>
</state>
</channel-type>
<channel-type id="acunit-homekitmode">
<item-type>String</item-type>
<label>Homekit Mode</label>
<description>Current Homekit mode of the AC unit</description>
<state>
<options>
<option value="auto">Auto</option>
<option value="heat">Heating On</option>
<option value="cool">Cooling On</option>
<option value="off">Off</option>
</options>
</state>
</channel-type>
<channel-type id="acunit-fan">
<item-type>String</item-type>
<label>Fan</label>
<description>Current fan speed setting of the AC unit</description>
<state>
<options>
<option value="AUTO">auto</option>
<option value="SILENCE">silence</option>
<option value="LEVEL_1">level 1</option>
<option value="LEVEL_2">level 2</option>
<option value="LEVEL_3">level 3</option>
<option value="LEVEL_4">level 4</option>
<option value="LEVEL_5">level 5</option>
</options>
</state>
</channel-type>
<channel-type id="acunit-fandir">
<item-type>String</item-type>
<label>Fan Swing</label>
<description>Current fan swing setting of the AC unit</description>
<state>
<options>
<option value="STOPPED">stopped</option>
<option value="VERTICAL">vertical</option>
<option value="HORIZONTAL">horizontal</option>
<option value="VERTICAL_AND_HORIZONTAL">vertical and horizontal</option>
</options>
</state>
</channel-type>
<channel-type id="acunit-cmpfrequency" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Compressor Frequency</label>
<description>Current compressor frequency</description>
<state readOnly="true" pattern="%.0f %unit%"></state>
</channel-type>
<channel-type id="acunit-specialmode" advanced="true">
<item-type>String</item-type>
<label>Special Mode</label>
<description>Current special mode</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="acunit-specialmode-powerful" advanced="true">
<item-type>Switch</item-type>
<label>Powerful Mode</label>
<description>Powerful mode switch</description>
</channel-type>
<channel-type id="acunit-energyheatingcurrentyear-1" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Heating Current Year January</label>
<description>The energy usage for heating this year January</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="acunit-energyheatingcurrentyear-2" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Heating Current Year February</label>
<description>The energy usage for heating this year February</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="acunit-energyheatingcurrentyear-3" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Heating Current Year March</label>
<description>The energy usage for heating this year March</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="acunit-energyheatingcurrentyear-4" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Heating Current Year April</label>
<description>The energy usage for heating this year April</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="acunit-energyheatingcurrentyear-5" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Heating Current Year May</label>
<description>The energy usage for heating this year May</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="acunit-energyheatingcurrentyear-6" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Heating Current Year June</label>
<description>The energy usage for heating this year June</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="acunit-energyheatingcurrentyear-7" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Heating Current Year July</label>
<description>The energy usage for heating this year July</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="acunit-energyheatingcurrentyear-8" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Heating Current Year August</label>
<description>The energy usage for heating this year August</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="acunit-energyheatingcurrentyear-9" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Heating Current Year September</label>
<description>The energy usage for heating this year September</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="acunit-energyheatingcurrentyear-10" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Heating Current Year October</label>
<description>The energy usage for heating this year October</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="acunit-energyheatingcurrentyear-11" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Heating Current Year November</label>
<description>The energy usage for heating this year November</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="acunit-energyheatingcurrentyear-12" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Heating Current Year December</label>
<description>The energy usage for heating this year December</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="acunit-energycoolingcurrentyear-1" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Cooling Current Year January</label>
<description>The energy usage for cooling this year January</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="acunit-energycoolingcurrentyear-2" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Cooling Current Year February</label>
<description>The energy usage for cooling this year February</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="acunit-energycoolingcurrentyear-3" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Cooling Current Year March</label>
<description>The energy usage for cooling this year March</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="acunit-energycoolingcurrentyear-4" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Cooling Current Year April</label>
<description>The energy usage for cooling this year April</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="acunit-energycoolingcurrentyear-5" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Cooling Current Year May</label>
<description>The energy usage for cooling this year May</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="acunit-energycoolingcurrentyear-6" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Cooling Current Year June</label>
<description>The energy usage for cooling this year June</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="acunit-energycoolingcurrentyear-7" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Cooling Current Year July</label>
<description>The energy usage for cooling this year July</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="acunit-energycoolingcurrentyear-8" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Cooling Current Year August</label>
<description>The energy usage for cooling this year August</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="acunit-energycoolingcurrentyear-9" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Cooling Current Year September</label>
<description>The energy usage for cooling this year September</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="acunit-energycoolingcurrentyear-10" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Cooling Current Year October</label>
<description>The energy usage for cooling this year October</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="acunit-energycoolingcurrentyear-11" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Cooling Current Year November</label>
<description>The energy usage for cooling this year November</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="acunit-energycoolingcurrentyear-12" advanced="true">
<item-type>Number:Energy</item-type>
<label>Energy Cooling Current Year December</label>
<description>The energy usage for cooling this year December</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="airbase-acunit-fan">
<item-type>String</item-type>
<label>Fan</label>
<description>Current fan speed setting of the AC unit</description>
</channel-type>
<channel-type id="airbase-acunit-zone1">
<item-type>Switch</item-type>
<label>Zone 1</label>
<description>Zone 1 for the AC unit</description>
</channel-type>
<channel-type id="airbase-acunit-zone2">
<item-type>Switch</item-type>
<label>Zone 2</label>
<description>Zone 2 for the AC unit</description>
</channel-type>
<channel-type id="airbase-acunit-zone3">
<item-type>Switch</item-type>
<label>Zone 3</label>
<description>Zone 3 for the AC unit</description>
</channel-type>
<channel-type id="airbase-acunit-zone4">
<item-type>Switch</item-type>
<label>Zone 4</label>
<description>Zone 4 for the AC unit</description>
</channel-type>
<channel-type id="airbase-acunit-zone5">
<item-type>Switch</item-type>
<label>Zone 5</label>
<description>Zone 5 for the AC unit</description>
</channel-type>
<channel-type id="airbase-acunit-zone6">
<item-type>Switch</item-type>
<label>Zone 6</label>
<description>Zone 6 for the AC unit</description>
</channel-type>
<channel-type id="airbase-acunit-zone7">
<item-type>Switch</item-type>
<label>Zone 7</label>
<description>Zone 7 for the AC unit</description>
</channel-type>
<channel-type id="airbase-acunit-zone8">
<item-type>Switch</item-type>
<label>Zone 8</label>
<description>Zone 8 for the AC unit</description>
</channel-type>
</thing:thing-descriptions>