added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -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>
|
||||
@@ -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()));
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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("&"));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user