added migrated 2.x add-ons

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

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.intesis</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@@ -0,0 +1,92 @@
# Intesis Binding
This binding connects to WiFi [IntesisHome](http://www.intesishome.com/) devices using their local REST Api.
It does actually not support [IntesisBox](http://www.intesisbox.com/) devices but support is planned in upcoming version.
## Supported Things
This binding only supports one thing type:
| Thing | Thing Type | Description |
|------------ |------------|---------------------------------|
| intesisHome | Thing | Represents a single WiFi device |
## Discovery
Intesis devices do not support auto discovery.
## Thing Configuration
The binding needs two configuration parameters.
| Parameter | Description |
|-----------|---------------------------------------------------|
| ipAddress | IP-Address of the device |
| password | Password to login to the local webserver of device |
## Channels
| Channel ID | Item Type | Description | Possible Values |
|--------------------|--------------------|---------------------------------------------|---------------------------|
| power | Switch | Turns power on/off for your climate system. | ON, OFF |
| mode | String | The heating/cooling mode. | AUTO,HEAT,DRY,FAN,COOL |
| fanSpeed | String | Fan speed (if applicable) | AUTO,1-10 |
| vanesUpDown | String | Control of up/down vanes (if applicable) | AUTO,1-9,SWING,SWIRL,WIDE |
| vanesUpDown | String | Control of left/right vanes (if applicable) | AUTO,1-9,SWING,SWIRL,WIDE |
| targetTemperature | Number:Temperature | The currently set target temperature. | |
| ambientTemperature | Number:Temperature | (Readonly) The ambient air temperature. | |
| outdoorTemperature | Number:Temperature | (Readonly) The outdoor air temperature. | |
Note that individual A/C units may not support all channels, or all possible values for those channels.
The binding will add all supported channels and possible values on first thing initialization and list them as thing properties.
If new channels or values might be supported after firmware upgrades, deleting the thing and reading is necessary.
For example, not all A/C units have controllable vanes. Or fan speed may be limited to 1-4, instead of all of 1-9.
The set point temperature is also limited to a device specific range. For set point temperature, sending an invalid value
will cause it to choose the minimum/maximum allowable value as appropriate. The device will also round it to
whatever step size it supports. For all other channels, invalid values are ignored.
## Full Example
The binding can be fully setup from the UI but if you decide to use files here is a full example:
**Things**
```intesisHome.things
Thing intesis:intesisHome:acOffice "AC Unit Adapter" @ "AC" [ipAddress="192.168.1.100", password="xxxxx"]
```
**Items**
```intesishome.items
Switch ac "Power" { channel="intesis:intesisHome:acOffice:power" }
String acMode "Mode" { channel="intesis:intesisHome:acOffice:mode" }
String acFanSpeed "Fan Speed" <fan> { channel="intesis:intesisHome:acOffice:fanSpeed" }
String acVanesUpDown "Vanes Up/Ddown Position" { channel="intesis:intesisHome:acOffice:vanesUpDown" }
String acVanesLeftRight "Vanes Left/Right Position" { channel="intesis:intesisHome:acOffice:vanesLeftRight" }
Number:Temperature acSetPoint "Target Temperature" <heating> { channel="intesis:intesisHome:acOffice:targetTemperature" }
Number:Temperature acAmbientTemp "Ambient Temperature" <temperature> { channel="intesis:intesisHome:acOffice:ambientTemperature" }
Number:Temperature acOutdoorTemp "Outdoor Temperature" <temperature> { channel="intesis:intesisHome:acOffice:outdoorTemperature" }
```
**Sitemap**
```intesisHome.sitemap
sitemap intesishome label="My AC control" {
Frame label="Climate" {
Switch item=ac
Switch item=acMode icon="heating" mappings=[AUTO="Auto", HEAT="Heat", DRY="Dry", FAN="Fan", COOL="Cool"]
Switch item=acFanSpeed icon="qualityofservice" mappings=[AUTO="Auto", 1="Low", 2="Med", 3="MedHigh", 4="High"]
Switch item=acVanesUpDown icon="movecontrol" mappings=[AUTO="Stop", 1="1", 2="2", 3="3", 4="4", 5="5", SWING="Swing"]
Switch item=acVanesLeftRight icon="movecontrol" mappings=[AUTO="Stop", 1="1", 2="2", 3="3", 4="4", 5="5", SWING="Swing"]
Setpoint item=acSetPoint icon="temperature" minValue=16 maxValue=28 step=1
Text item=acAmbientTemp icon="temperature"
Text item=acOutdoorTemp icon="temperature"
}
}
```

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.intesis</artifactId>
<name>openHAB Add-ons :: Bundles :: Intesis Binding</name>
</project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.intesis-${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-intesis" description="Intesis Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.intesis/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,44 @@
/**
* 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.intesis.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link IntesisBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Hans-Jörg Merk - Initial contribution
*/
@NonNullByDefault
public class IntesisBindingConstants {
public static final String BINDING_ID = "intesis";
public static final int INTESIS_HTTP_API_TIMEOUT_MS = 5000;
public static final int INTESIS_REFRESH_INTERVAL_SEC = 30;
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_INTESISHOME = new ThingTypeUID(BINDING_ID, "intesisHome");
// List of all Channel ids
public static final String CHANNEL_TYPE_POWER = "power";
public static final String CHANNEL_TYPE_MODE = "mode";
public static final String CHANNEL_TYPE_FANSPEED = "fanSpeed";
public static final String CHANNEL_TYPE_VANESUD = "vanesUpDown";
public static final String CHANNEL_TYPE_VANESLR = "vanesLeftRight";
public static final String CHANNEL_TYPE_TARGETTEMP = "targetTemperature";
public static final String CHANNEL_TYPE_AMBIENTTEMP = "ambientTemperature";
public static final String CHANNEL_TYPE_OUTDOORTEMP = "outdoorTemperature";
}

View File

@@ -0,0 +1,26 @@
/**
* 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.intesis.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link IntesisConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Hans-Jörg Merk - Initial contribution
*/
@NonNullByDefault
public class IntesisConfiguration {
public String ipAddress = "";
public String password = "";
}

View File

@@ -0,0 +1,28 @@
/**
* 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.intesis.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
import org.osgi.service.component.annotations.Component;
/**
*
* @author Hans-Jörg Merk - Initial contribution
*/
@Component(service = { DynamicStateDescriptionProvider.class, IntesisDynamicStateDescriptionProvider.class })
@NonNullByDefault
public class IntesisDynamicStateDescriptionProvider extends BaseDynamicStateDescriptionProvider {
}

View File

@@ -0,0 +1,78 @@
/**
* 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.intesis.internal;
import static org.openhab.binding.intesis.internal.IntesisBindingConstants.THING_TYPE_INTESISHOME;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.intesis.internal.handler.IntesisHomeHandler;
import org.openhab.core.io.net.http.HttpClientFactory;
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.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link IntesisHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Hans-Jörg Merk - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.intesis", service = ThingHandlerFactory.class)
public class IntesisHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(IntesisHandlerFactory.class);
private final HttpClient httpClient;
private final IntesisDynamicStateDescriptionProvider intesisStateDescriptionProvider;
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_INTESISHOME);
@Activate
public IntesisHandlerFactory(@Reference HttpClientFactory httpClientFactory, ComponentContext componentContext,
final @Reference IntesisDynamicStateDescriptionProvider dynamicStateDescriptionProvider) {
super.activate(componentContext);
this.httpClient = httpClientFactory.getCommonHttpClient();
this.intesisStateDescriptionProvider = dynamicStateDescriptionProvider;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_INTESISHOME.equals(thingTypeUID)) {
logger.debug("Creating a IntesisHomeHandler for thing '{}'", thing.getUID());
return new IntesisHomeHandler(thing, httpClient, intesisStateDescriptionProvider);
}
return null;
}
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.intesis.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link IntesisModeEnum) contains informations for translating device modes into internally used numbers.
*
* @author Hans-Jörg Merk - Initial contribution
*/
@NonNullByDefault
public enum IntesisHomeModeEnum {
AUTO(0),
HEAT(1),
DRY(2),
FAN(3),
COOL(4);
private final int mode;
private IntesisHomeModeEnum(int mode) {
this.mode = mode;
}
public int getMode() {
return mode;
}
}

View File

@@ -0,0 +1,76 @@
/**
* 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.intesis.internal.api;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
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.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpHeader;
import org.openhab.binding.intesis.internal.IntesisConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link IntesisHomeHttpApi} wraps the IntesisHome REST API and provides various low level function to access the
* device api (not cloud api).
*
* @author Hans-Jörg Merk - Initial contribution
*/
@NonNullByDefault
public class IntesisHomeHttpApi {
public static final String CONTENT_TYPE_JSON = "application/json; charset=UTF-8";
private final Logger logger = LoggerFactory.getLogger(IntesisHomeHttpApi.class);
private final HttpClient httpClient;
public IntesisHomeHttpApi(IntesisConfiguration config, HttpClient httpClient) {
this.httpClient = httpClient;
}
/**
* Used to post a request to the device
*
* @param ipAddress of the device
* @param content string
* @return JSON string as response
*/
@Nullable
public String postRequest(String ipAddress, String contentString) {
String url = "http://" + ipAddress + "/api.cgi";
try {
Request request = httpClient.POST(url);
request.header(HttpHeader.CONTENT_TYPE, "application/json");
request.content(new StringContentProvider(contentString), "application/json");
ContentResponse contentResponse = request.timeout(5, TimeUnit.SECONDS).send();
String response = contentResponse.getContentAsString().replace("\t", "").replace("\r\n", "").trim();
if (response != null && !response.isEmpty()) {
return response;
} else {
return null;
}
} catch (TimeoutException | InterruptedException | ExecutionException e) {
logger.debug("Could not make HTTP Post request");
}
return null;
}
}

View File

@@ -0,0 +1,123 @@
/**
* 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.intesis.internal.gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
/**
* {@link IntesisHomeJSonDTO} is used for the JSon/GSon mapping
*
* @author Hans-Jörg Merk - Initial contribution
*/
public class IntesisHomeJSonDTO {
public static class Response {
public boolean success;
public JsonElement data;
}
public static class Data {
public JsonElement id;
public JsonElement info;
public JsonElement userinfo;
public JsonElement config;
public JsonElement dp;
public JsonElement dpval;
}
public static class Id {
public String sessionID; // Session ID
}
// Device Information used for thing properties
public static class Info {
public String wlanSTAMAC; // Device Client MAC Address
public String wlanAPMAC; // Device Access Point MAC Address
public String fwVersion; // Device Firmware Version
public String wlanFwVersion; // Wireless Firmware Version
public String acStatus; // Air Conditioner Communication Status
public String wlanLNK; // Connection Status with Wireless Network
public String ssid; // Wireless Network SSID
public String rssi; // Wireless Signal Strength
public String tcpServerLNK; // Cloud Server Connection (Not used for communication here)
public String localdatetime; // Local Date Time
public String powerStatus;
public String lastconfigdatetime; // Last Configuration Date Time
public String deviceModel; // Device Model
public String sn; // Serial Number
public String lastError;
}
public static class Userinfo {
public String username;
public JsonElement servicelist;
}
// List of available services
public static class Servicelist {
public String setconfig;
public String getconfig;
public String getcurrentconfig;
public String getinfo;
public String login;
public String logout;
public String passchange;
public String getavailabledatapoints;
public String setdatapointvalue;
public String getdatapointvalue;
public String getavailableservices;
public String reboot;
public String setdefaults;
public String getdefaultconfig;
}
public static class Config {
public String deviceModel; // Device Model
public String ip; // Device IP Address
public String netmask; // Device IP Address
public String dfltgw; // Default gateway
public boolean dhcp; // DHCP enabled
public String ssid; // WLAN Access Point
public int security; // Security Type
public int regdomain;
public int lastconfigdatetime;
}
public static class Dp {
public JsonArray datapoints; // dataPoints
}
// Array of UIDs with corresponding description for dynamic channel creation
public static class Datapoints {
public int uid; // dataPoint
public String rw; // read/write status
public int type;
public JsonElement descr;
}
// Descriptor of dataPoint values
public static class Descr {
public int numStates;
public String[] states;
public String maxValue;
public String minValue;
}
// Array of UIDs with corresponding values, mapped into channel
public static class Dpval {
public int uid; // ID
public int value;
public int status;
}
}

View File

@@ -0,0 +1,499 @@
/**
* 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.intesis.internal.handler;
import static org.openhab.binding.intesis.internal.IntesisBindingConstants.*;
import static org.openhab.core.thing.Thing.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
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.openhab.binding.intesis.internal.IntesisConfiguration;
import org.openhab.binding.intesis.internal.IntesisDynamicStateDescriptionProvider;
import org.openhab.binding.intesis.internal.IntesisHomeModeEnum;
import org.openhab.binding.intesis.internal.api.IntesisHomeHttpApi;
import org.openhab.binding.intesis.internal.gson.IntesisHomeJSonDTO.Data;
import org.openhab.binding.intesis.internal.gson.IntesisHomeJSonDTO.Datapoints;
import org.openhab.binding.intesis.internal.gson.IntesisHomeJSonDTO.Descr;
import org.openhab.binding.intesis.internal.gson.IntesisHomeJSonDTO.Dp;
import org.openhab.binding.intesis.internal.gson.IntesisHomeJSonDTO.Dpval;
import org.openhab.binding.intesis.internal.gson.IntesisHomeJSonDTO.Id;
import org.openhab.binding.intesis.internal.gson.IntesisHomeJSonDTO.Info;
import org.openhab.binding.intesis.internal.gson.IntesisHomeJSonDTO.Response;
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.Channel;
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.binding.BaseThingHandler;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.thing.type.ChannelKind;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.StateOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
/**
* The {@link IntesisHomeHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Hans-Jörg Merk - Initial contribution
*/
@NonNullByDefault
public class IntesisHomeHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(IntesisHomeHandler.class);
private final IntesisHomeHttpApi api;
private final Map<String, String> properties = new HashMap<>();
private final IntesisDynamicStateDescriptionProvider intesisStateDescriptionProvider;
private final Gson gson = new Gson();
private IntesisConfiguration config = new IntesisConfiguration();
private @Nullable ScheduledFuture<?> refreshJob;
public IntesisHomeHandler(final Thing thing, final HttpClient httpClient,
IntesisDynamicStateDescriptionProvider intesisStateDescriptionProvider) {
super(thing);
this.api = new IntesisHomeHttpApi(config, httpClient);
this.intesisStateDescriptionProvider = intesisStateDescriptionProvider;
}
@Override
public void initialize() {
updateStatus(ThingStatus.UNKNOWN);
config = getConfigAs(IntesisConfiguration.class);
if (config.ipAddress.isEmpty() && config.password.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "IP-Address and password not set");
return;
} else if (config.ipAddress.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "IP-Address not set");
return;
} else if (config.password.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Password not set");
return;
} else {
// start background initialization:
scheduler.submit(() -> {
populateProperties();
// query available dataPoints and build dynamic channels
postRequestInSession(sessionId -> "{\"command\":\"getavailabledatapoints\",\"data\":{\"sessionID\":\""
+ sessionId + "\"}}", this::handleDataPointsResponse);
updateProperties(properties);
});
}
}
@Override
public void dispose() {
logger.debug("IntesisHomeHandler disposed.");
final ScheduledFuture<?> refreshJob = this.refreshJob;
if (refreshJob != null) {
refreshJob.cancel(true);
this.refreshJob = null;
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
int uid = 0;
int value = 0;
String channelId = channelUID.getId();
if (command instanceof RefreshType) {
// Refresh command is not supported as the binding polls all values every 30 seconds
} else {
switch (channelId) {
case CHANNEL_TYPE_POWER:
uid = 1;
value = command.equals(OnOffType.OFF) ? 0 : 1;
break;
case CHANNEL_TYPE_MODE:
uid = 2;
value = IntesisHomeModeEnum.valueOf(command.toString()).getMode();
break;
case CHANNEL_TYPE_FANSPEED:
uid = 4;
if (("AUTO").equals(command.toString())) {
value = 0;
} else {
value = Integer.parseInt(command.toString());
}
break;
case CHANNEL_TYPE_VANESUD:
case CHANNEL_TYPE_VANESLR:
switch (command.toString()) {
case "AUTO":
value = 0;
break;
case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7":
case "8":
case "9":
value = Integer.parseInt(command.toString());
break;
case "SWING":
value = 10;
break;
case "SWIRL":
value = 11;
break;
case "WIDE":
value = 12;
break;
}
switch (channelId) {
case CHANNEL_TYPE_VANESUD:
uid = 5;
break;
case CHANNEL_TYPE_VANESLR:
uid = 6;
break;
}
break;
case CHANNEL_TYPE_TARGETTEMP:
uid = 9;
if (command instanceof QuantityType) {
QuantityType<?> newVal = (QuantityType<?>) command;
newVal = newVal.toUnit(SIUnits.CELSIUS);
if (newVal != null) {
value = newVal.intValue() * 10;
}
}
break;
}
}
if (uid != 0) {
final int uId = uid;
final int newValue = value;
scheduler.submit(() -> {
postRequestInSession(
sessionId -> "{\"command\":\"setdatapointvalue\",\"data\":{\"sessionID\":\"" + sessionId
+ "\", \"uid\":" + uId + ",\"value\":" + newValue + "}}",
r -> updateStatus(ThingStatus.ONLINE));
});
}
}
public @Nullable String login() {
// lambda's can't modify local variables, so we use an array here to get around the issue
String[] sessionId = new String[1];
postRequest(
"{\"command\":\"login\",\"data\":{\"username\":\"Admin\",\"password\":\"" + config.password + "\"}}",
resp -> {
Data data = gson.fromJson(resp.data, Data.class);
Id id = gson.fromJson(data.id, Id.class);
sessionId[0] = id.sessionID.toString();
});
if (sessionId[0] != null && !sessionId[0].isEmpty()) {
updateStatus(ThingStatus.ONLINE);
return sessionId[0];
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "SessionId not received");
return null;
}
}
public @Nullable String logout(String sessionId) {
String contentString = "{\"command\":\"logout\",\"data\":{\"sessionID\":\"" + sessionId + "\"}}";
String response = api.postRequest(config.ipAddress, contentString);
return response;
}
public void populateProperties() {
postRequest("{\"command\":\"getinfo\",\"data\":\"\"}", resp -> {
Data data = gson.fromJson(resp.data, Data.class);
Info info = gson.fromJson(data.info, Info.class);
properties.put(PROPERTY_VENDOR, "Intesis");
properties.put(PROPERTY_MODEL_ID, info.deviceModel);
properties.put(PROPERTY_SERIAL_NUMBER, info.sn);
properties.put(PROPERTY_FIRMWARE_VERSION, info.fwVersion);
properties.put(PROPERTY_MAC_ADDRESS, info.wlanSTAMAC);
updateStatus(ThingStatus.ONLINE);
});
}
public void addChannel(String channelId, String itemType, @Nullable final Collection<String> options) {
if (thing.getChannel(channelId) == null) {
logger.trace("Channel '{}' for UID to be added", channelId);
ThingBuilder thingBuilder = editThing();
final ChannelTypeUID channelTypeUID = new ChannelTypeUID(BINDING_ID, channelId);
Channel channel = ChannelBuilder.create(new ChannelUID(getThing().getUID(), channelId), itemType)
.withType(channelTypeUID).withKind(ChannelKind.STATE).build();
thingBuilder.withChannel(channel);
updateThing(thingBuilder.build());
if (options != null) {
final List<StateOption> stateOptions = options.stream()
.map(e -> new StateOption(e, e.substring(0, 1) + e.substring(1).toLowerCase()))
.collect(Collectors.toList());
logger.trace("StateOptions : '{}'", stateOptions);
intesisStateDescriptionProvider.setStateOptions(channel.getUID(), stateOptions);
}
}
}
private void postRequest(String request, Consumer<Response> handler) {
try {
logger.trace("request : '{}'", request);
String response = api.postRequest(config.ipAddress, request);
if (response != null) {
Response resp = gson.fromJson(response, Response.class);
boolean success = resp.success;
if (success) {
handler.accept(resp);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Request unsuccessful");
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "No Response");
}
} catch (JsonSyntaxException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
private void postRequestInSession(UnaryOperator<String> requestFactory, Consumer<Response> handler) {
String sessionId = login();
if (sessionId != null) {
try {
String request = requestFactory.apply(sessionId);
postRequest(request, handler);
} finally {
logout(sessionId);
}
}
}
private void handleDataPointsResponse(Response response) {
try {
Data data = gson.fromJson(response.data, Data.class);
Dp dp = gson.fromJson(data.dp, Dp.class);
Datapoints[] datapoints = gson.fromJson(dp.datapoints, Datapoints[].class);
for (Datapoints datapoint : datapoints) {
Descr descr = gson.fromJson(datapoint.descr, Descr.class);
String channelId = "";
String itemType = "String";
switch (datapoint.uid) {
case 2:
List<String> opModes = new ArrayList<>();
for (String modString : descr.states) {
switch (modString) {
case "0":
opModes.add("AUTO");
break;
case "1":
opModes.add("HEAT");
break;
case "2":
opModes.add("DRY");
break;
case "3":
opModes.add("FAN");
break;
case "4":
opModes.add("COOL");
break;
}
}
properties.put("Supported modes", opModes.toString());
channelId = CHANNEL_TYPE_MODE;
addChannel(channelId, itemType, opModes);
break;
case 4:
List<String> fanLevels = new ArrayList<>();
for (String fanString : descr.states) {
if ("AUTO".contentEquals(fanString)) {
fanLevels.add("AUTO");
} else {
fanLevels.add(fanString);
}
}
properties.put("Supported fan levels", fanLevels.toString());
channelId = CHANNEL_TYPE_FANSPEED;
addChannel(channelId, itemType, fanLevels);
break;
case 5:
case 6:
List<String> swingModes = new ArrayList<>();
for (String swingString : descr.states) {
if ("AUTO".contentEquals(swingString)) {
swingModes.add("AUTO");
} else if ("10".contentEquals(swingString)) {
swingModes.add("SWING");
} else if ("11".contentEquals(swingString)) {
swingModes.add("SWIRL");
} else if ("12".contentEquals(swingString)) {
swingModes.add("WIDE");
} else {
swingModes.add(swingString);
}
}
switch (datapoint.uid) {
case 5:
channelId = CHANNEL_TYPE_VANESUD;
properties.put("Supported vane up/down modes", swingModes.toString());
addChannel(channelId, itemType, swingModes);
break;
case 6:
channelId = CHANNEL_TYPE_VANESLR;
properties.put("Supported vane left/right modes", swingModes.toString());
addChannel(channelId, itemType, swingModes);
break;
}
break;
case 9:
channelId = CHANNEL_TYPE_TARGETTEMP;
itemType = "Number:Temperature";
addChannel(channelId, itemType, null);
break;
case 10:
channelId = CHANNEL_TYPE_AMBIENTTEMP;
itemType = "Number:Temperature";
addChannel(channelId, itemType, null);
break;
case 37:
channelId = CHANNEL_TYPE_OUTDOORTEMP;
itemType = "Number:Temperature";
addChannel(channelId, itemType, null);
break;
}
}
} catch (JsonSyntaxException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
logger.trace("Start Refresh Job");
refreshJob = scheduler.scheduleWithFixedDelay(this::getAllUidValues, 0, INTESIS_REFRESH_INTERVAL_SEC,
TimeUnit.SECONDS);
}
/**
* Update device status and all channels
*/
private void getAllUidValues() {
postRequestInSession(sessionId -> "{\"command\":\"getdatapointvalue\",\"data\":{\"sessionID\":\"" + sessionId
+ "\", \"uid\":\"all\"}}", this::handleDataPointValues);
}
private void handleDataPointValues(Response response) {
try {
Data data = gson.fromJson(response.data, Data.class);
Dpval[] dpval = gson.fromJson(data.dpval, Dpval[].class);
for (Dpval element : dpval) {
logger.trace("UID : {} ; value : {}", element.uid, element.value);
switch (element.uid) {
case 1:
updateState(CHANNEL_TYPE_POWER,
String.valueOf(element.value).equals("0") ? OnOffType.OFF : OnOffType.ON);
break;
case 2:
switch (element.value) {
case 0:
updateState(CHANNEL_TYPE_MODE, StringType.valueOf("AUTO"));
break;
case 1:
updateState(CHANNEL_TYPE_MODE, StringType.valueOf("HEAT"));
break;
case 2:
updateState(CHANNEL_TYPE_MODE, StringType.valueOf("DRY"));
break;
case 3:
updateState(CHANNEL_TYPE_MODE, StringType.valueOf("FAN"));
break;
case 4:
updateState(CHANNEL_TYPE_MODE, StringType.valueOf("COOL"));
break;
}
break;
case 4:
if ((element.value) == 0) {
updateState(CHANNEL_TYPE_FANSPEED, StringType.valueOf("AUTO"));
} else {
updateState(CHANNEL_TYPE_FANSPEED, StringType.valueOf(String.valueOf(element.value)));
}
break;
case 5:
case 6:
State state;
if ((element.value) == 0) {
state = StringType.valueOf("AUTO");
} else if ((element.value) == 10) {
state = StringType.valueOf("SWING");
} else if ((element.value) == 11) {
state = StringType.valueOf("SWIRL");
} else if ((element.value) == 12) {
state = StringType.valueOf("WIDE");
} else {
state = StringType.valueOf(String.valueOf(element.value));
}
switch (element.uid) {
case 5:
updateState(CHANNEL_TYPE_VANESUD, state);
break;
case 6:
updateState(CHANNEL_TYPE_VANESLR, state);
break;
}
break;
case 9:
int unit = Math.round((element.value) / 10);
State stateValue = QuantityType.valueOf(unit, SIUnits.CELSIUS);
updateState(CHANNEL_TYPE_TARGETTEMP, stateValue);
break;
case 10:
unit = Math.round((element.value) / 10);
stateValue = QuantityType.valueOf(unit, SIUnits.CELSIUS);
updateState(CHANNEL_TYPE_AMBIENTTEMP, stateValue);
break;
case 37:
unit = Math.round((element.value) / 10);
stateValue = QuantityType.valueOf(unit, SIUnits.CELSIUS);
updateState(CHANNEL_TYPE_OUTDOORTEMP, stateValue);
break;
}
}
} catch (JsonSyntaxException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="intesis" 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>Intesis Binding</name>
<description>This is the binding for Intesis WiFi adapters used in various AC devices.</description>
<author>Hans-Jörg Merk</author>
</binding:binding>

View File

@@ -0,0 +1,42 @@
# binding
binding.intesis.name = Intesis Binding
binding.intesis.description = This binding integrates Intesis WiFi devices to control various air conditioners
# thing types
thing-type.intesis.intesisHome.label = IntesisHome
thing-type.intesis.sample.description = IntesisHome WIFI adapter
# thing type config description
thing-type.config.intesis.ipAddress.label = IP Address
thing-type.config.intesis.ipAddress.description = IP Address of the Intesis device.
thing-type.config.intesisHome.password.label = Password
thing-type.config.intesisHome.password.description = Password to log into the IntesisHome device Web-Server.
# channel types
channel-type.intesis.power.label = Power
channel-type.intesis.power.description = Turn power on/off
channel-type.intesis.mode.label = Unit Mode
channel-type.intesis.mode.description = Operating mode of the Air Conditioner: AUTO/HEAT/DRY/FAN/COOL
channel-type.intesis.mode.state.option.auto = Auto
channel-type.intesis.mode.state.option.cool = Cool
channel-type.intesis.mode.state.option.dry = Dry
channel-type.intesis.mode.state.option.fan = Fan
channel-type.intesis.mode.state.option.heat = Heat
channel-type.intesis.targetTemperature.label = Target Temperature
channel-type.intesis.targetTemperature.description = Sets the desired room temperature.
channel-type.intesis.ambientTemperature.label = Ambient Temperature
channel-type.intesis.ambientTemperature.description = Shows actual room temperature.
channel-type.intesis.outdoorTemperature.label = Outdoor Temperature
channel-type.intesis.outdoorTemperature.description = Shows actual outdoor temperature.
channel-type.intesis.fanSpeed.label = Wind Speed
channel-type.intesis.fanSpeed.description = Sets the fan speed on the Air conditioner.
channel-type.intesis.fanSpeed.state.option.auto = Auto
channel-type.intesis.vanesUpDown.label = Vertical Swing Mode
channel-type.intesis.vanesUpDown.description = Sets the vertical (up/down)swing action on the Air Conditioner.
channel-type.intesis.vanesLeftRight.label = Horizontal Swing Mode
channel-type.intesis.vanesLeftRight.description = Sets the horizontal (left/right) swing action on the Air Conditioner.
channel-type.intesis.vanes.option.auto = AUTO
channel-type.intesis.vanes.option.swing = Swing
channel-type.intesis.vanes.option.swirl = Swirl
channel-type.intesis.vanes.option.wide = Wide

View File

@@ -0,0 +1,42 @@
# binding
binding.intesis.name = Intesis Binding
binding.intesis.description = Das Intesis Binding integriert die Intesis WiFi Adapter, welche in diversen Klimageräten verwendet werden.
# thing types
thing-type.intesis.intesisHome.label = IntesisHome
thing-type.intesis.sample.description = IntesisHome WIFI Adapter
# thing type config description
thing-type.config.intesis.ipAddress.label = IP Addresse
thing-type.config.intesis.ipAddress.description = IP Addresse des Intesis Adapters.
thing-type.config.intesisHome.password.label = Passwort
thing-type.config.intesisHome.password.description = Password zum Login in den lokalen Web-Server des IntesisHome Adapters.
# channel types
channel-type.intesis.power.label = Betrieb
channel-type.intesis.power.description = Schaltet das Gerät Ein oder Aus.
channel-type.intesis.mode.label = Betriebsart
channel-type.intesis.mode.description = Betriebsart des Klimageräts: Auto/Heizen/Trocknen/Ventilation/Kühlen
channel-type.intesis.mode.state.option.auto = Auto
channel-type.intesis.mode.state.option.cool = Kühlen
channel-type.intesis.mode.state.option.dry = Trocknen
channel-type.intesis.mode.state.option.fan = Ventilation
channel-type.intesis.mode.state.option.heat = Heizen
channel-type.intesis.targetTemperature.label = SollTemperatur
channel-type.intesis.targetTemperature.description = Stellt die gewünschte Solltemperatur ein.
channel-type.intesis.ambientTemperature.label = Umgebungstemperatur
channel-type.intesis.ambientTemperature.description = Zeigt die aktuelle Raumtemperatur an.
channel-type.intesis.outdoorTemperature.label = Außentemperatur
channel-type.intesis.outdoorTemperature.description = Zeigt die aktuelle Außentemperatur an.
channel-type.intesis.fanSpeed.label = Ventilatorgeschwindigkeit
channel-type.intesis.fanSpeed.description = Stellt die gewüschte Ventilatorgeschwindigkeit des Klimageräts ein.
channel-type.intesis.fanSpeed.state.option.auto = Auto
channel-type.intesis.vanesUpDown.label = Vertikale (Auf/Ab) Lamelleneinstellung
channel-type.intesis.vanesUpDown.description = Stellt die vertikale Lamellensteuerung ein.
channel-type.intesis.vanesLeftRight.label = Horizontale (Links/Rechts) Lamelleneinstellung
channel-type.intesis.vanesLeftRight.description = Stellt die horizontale Lamellensteuerung ein.
channel-type.intesis.vanes.option.auto = Auto
channel-type.intesis.vanes.option.swing = Schwingen
channel-type.intesis.vanes.option.swirl = Pulsieren
channel-type.intesis.vanes.option.wide = Breit

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="intesis"
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">
<channel-type id="mode">
<item-type>String</item-type>
<label>@text/channel-type.intesis.mode.label</label>
<description>@text/channel-type.intesis.mode.description</description>
</channel-type>
<channel-type id="fanSpeed">
<item-type>String</item-type>
<label>@text/channel-type.intesis.fanSpeed.label</label>
<description>@text/channel-type.intesis.fanSpeed.description</description>
</channel-type>
<channel-type id="vanesUpDown">
<item-type>String</item-type>
<label>@text/channel-type.intesis.vanesUpDown.label</label>
<description>@text/channel-type.intesis.vanesUpDown.description</description>
</channel-type>
<channel-type id="vanesLeftRight">
<item-type>String</item-type>
<label>@text/channel-type.intesis.vanesLeftRight.label</label>
<description>@text/channel-type.intesis.vanesLeftRight.description</description>
</channel-type>
<channel-type id="targetTemperature">
<item-type>Number:Temperature</item-type>
<label>@text/channel-type.intesis.targetTemperature.label</label>
<description>@text/channel-type.intesis.targetTemperature.description</description>
<state pattern="%d %unit%"></state>
</channel-type>
<channel-type id="ambientTemperature">
<item-type>Number:Temperature</item-type>
<label>@text/channel-type.intesis.ambientTemperature.label</label>
<description>@text/channel-type.intesis.ambientTemperature.description</description>
<state pattern="%d %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="outdoorTemperature">
<item-type>Number:Temperature</item-type>
<label>@text/channel-type.intesis.outdoorTemperature.label</label>
<description>@text/channel-type.intesis.outdoorTemperature.description</description>
<state pattern="%d %unit%" readOnly="true"></state>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="intesis"
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="intesisHome">
<label>IntesisHome WiFi Adapter</label>
<description>IntesisHome WiFi Adapter</description>
<channels>
<channel id="power" typeId="system.power"/>
</channels>
<config-description>
<parameter name="ipAddress" type="text" required="true">
<label>@text/thing-type.config.intesis.ipAddress.label</label>
<description>@text/thing-type.config.intesis.ipAddress.description</description>
<context>network-address</context>
</parameter>
<parameter name="password" type="text" required="true">
<label>@text/thing-type.config.intesisHome.password.label</label>
<description>@text/thing-type.config.intesisHome.password.description</description>
<context>password</context>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>