[intesis] - added IntesisBox support (#8694)

* Intesis Binding - added IntesisBox support

Signed-off-by: Hans-Jörg Merk <github@hmerk.de>
This commit is contained in:
Hans-Jörg Merk 2020-10-24 20:17:56 +02:00 committed by GitHub
parent 8b8b79cf04
commit 91fbe746e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 900 additions and 43 deletions

View File

@ -1,7 +1,6 @@
# 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.
This binding connects to WiFi [IntesisHome](https://www.intesis.com/products/cloud-solutions/ac-cloud-control) devices using their local REST Api and to [IntesisBox](https://www.intesis.com/products/ac-interfaces/wifi-gateways) devices using TCP connection.
@ -9,9 +8,10 @@ It does actually not support [IntesisBox](http://www.intesisbox.com/) devices bu
This binding only supports one thing type:
| Thing | Thing Type | Description |
|------------ |------------|---------------------------------|
| intesisHome | Thing | Represents a single WiFi device |
| Thing | Thing Type | Description |
|-------------|------------|---------------------------------------------|
| intesisHome | Thing | Represents a single IntesisHome WiFi device |
| intesisBox | Thing | Represents a single IntesisBox WiFi device |
## Discovery
@ -19,35 +19,40 @@ Intesis devices do not support auto discovery.
## Thing Configuration
The binding needs two configuration parameters.
The binding uses the following configuration parameters.
| Parameter | Description |
|-----------|---------------------------------------------------|
| ipAddress | IP-Address of the device |
| password | Password to login to the local webserver of device |
| Parameter | Valid for ThingType | Description |
|-----------|---------------------|----------------------------------------------------------------|
| ipAddress | Both | IP-Address of the device |
| password | IntesisHome | Password to login to the local webserver of IntesisHome device |
| port | IntesisBox | TCP port to connect to IntesisBox device, defaults to 3310 |
## 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. | |
| 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 (if applicable) | range between 18°C and 30°C |
| ambientTemperature | Number:Temperature | (Readonly) The ambient air temperature (if applicable) | |
| outdoorTemperature | Number:Temperature | (Readonly) The outdoor air temperature (if applicable) | |
| errorStatus | String | (Readonly) The error status of the device | OK,ERR |
| errorCode | String | (Readonly) The error code if an error encountered | not documented |
| wifiSignal | Number | (Readonly) WiFi signal strength (IntesisBox only) | 4=excellent, 3=good, 2=not string, 1=unreliable, 0=none |
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
If new channels or values might be supported after firmware upgrades, deleting the thing and re-adding 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 target temperature is also limited to a device specific range. For target 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.
IntesisBox firmware 1.3.3 reports temperatures by full degrees only (e.g. 23.0) even if a half degree (e.g. 23.5) was set.
## Full Example
@ -55,8 +60,9 @@ The binding can be fully setup from the UI but if you decide to use files here i
**Things**
```intesisHome.things
```
Thing intesis:intesisHome:acOffice "AC Unit Adapter" @ "AC" [ipAddress="192.168.1.100", password="xxxxx"]
Thing intesis:intesisBox:acOffice "AC Unit Adapter" @ "AC" [ipAddress="192.168.1.100", port=3310]
```
**Items**
@ -89,4 +95,3 @@ sitemap intesishome label="My AC control" {
}
}
```

View File

@ -31,6 +31,7 @@ public class IntesisBindingConstants {
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_INTESISHOME = new ThingTypeUID(BINDING_ID, "intesisHome");
public static final ThingTypeUID THING_TYPE_INTESISBOX = new ThingTypeUID(BINDING_ID, "intesisBox");
// List of all Channel ids
public static final String CHANNEL_TYPE_POWER = "power";
@ -41,4 +42,7 @@ public class IntesisBindingConstants {
public static final String CHANNEL_TYPE_TARGETTEMP = "targetTemperature";
public static final String CHANNEL_TYPE_AMBIENTTEMP = "ambientTemperature";
public static final String CHANNEL_TYPE_OUTDOORTEMP = "outdoorTemperature";
public static final String CHANNEL_TYPE_ERRORCODE = "errorCode";
public static final String CHANNEL_TYPE_ERRORSTATUS = "errorStatus";
public static final String CHANNEL_TYPE_RSSI = "wifiSignal";
}

View File

@ -12,14 +12,17 @@
*/
package org.openhab.binding.intesis.internal;
import static org.openhab.binding.intesis.internal.IntesisBindingConstants.THING_TYPE_INTESISHOME;
import static org.openhab.binding.intesis.internal.IntesisBindingConstants.*;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.intesis.internal.handler.IntesisBoxHandler;
import org.openhab.binding.intesis.internal.handler.IntesisHomeHandler;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Thing;
@ -48,7 +51,8 @@ public class IntesisHandlerFactory extends BaseThingHandlerFactory {
private final HttpClient httpClient;
private final IntesisDynamicStateDescriptionProvider intesisStateDescriptionProvider;
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_INTESISHOME);
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(THING_TYPE_INTESISHOME, THING_TYPE_INTESISBOX).collect(Collectors.toSet()));
@Activate
public IntesisHandlerFactory(@Reference HttpClientFactory httpClientFactory, ComponentContext componentContext,
@ -69,10 +73,13 @@ public class IntesisHandlerFactory extends BaseThingHandlerFactory {
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);
}
if (THING_TYPE_INTESISBOX.equals(thingTypeUID)) {
return new IntesisBoxHandler(thing, intesisStateDescriptionProvider);
}
return null;
}
}

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.intesis.internal.api;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.ThingStatus;
/**
* The {@link IntesisBoxChangeListener} is in interface for a IntesisBox changed consumer
*
* @author Hans-Jörg Merk - Initial contribution
*/
@NonNullByDefault
public interface IntesisBoxChangeListener {
/**
* This method will be called in case a message was received.
*
*/
void messageReceived(String messageLine);
/**
* This method will be called in case the connection status has changed.
*
*/
void connectionStatusChanged(ThingStatus status, @Nullable String message);
}

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.api;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Cody Cutrer - Initial contribution
*/
@NonNullByDefault
public class IntesisBoxMessage {
public static final String ID = "ID";
public static final String INFO = "INFO";
public static final String SET = "SET";
public static final String CHN = "CHN";
public static final String GET = "GET";
public static final String LOGIN = "LOGIN";
public static final String LOGOUT = "LOGOUT";
public static final String CFG = "CFG";
public static final String LIMITS = "LIMITS";
public static final String DISCOVER = "DISCOVER";
private static final Pattern REGEX = Pattern.compile("^([^,]+)(?:,(\\d+))?:([^,]+),([A-Z0-9.,\\[\\]]+)$");
@SuppressWarnings("unused")
private final String acNum;
private final String command;
private final String function;
private final String value;
private IntesisBoxMessage(String command, String acNum, String function, String value) {
this.command = command;
this.acNum = acNum;
this.function = function;
this.value = value;
}
public String getCommand() {
return command;
}
public String getFunction() {
return function;
}
public String getValue() {
return value;
}
public List<String> getLimitsValue() {
return Arrays.asList(value.substring(1, value.length() - 1).split(","));
}
public static @Nullable IntesisBoxMessage parse(String message) {
Matcher m = REGEX.matcher(message);
if (!m.find()) {
return null;
}
return new IntesisBoxMessage(m.group(1), m.group(2), m.group(3), m.group(4));
}
}

View File

@ -0,0 +1,214 @@
/**
* 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.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.intesis.internal.handler.IntesisBoxHandler;
import org.openhab.core.thing.ThingStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class handling the Socket connections.
*
* @author Cody Cutrer - Initial contribution
* @author Hans-Jörg Merk - Moved Socket to it's own class
*/
@NonNullByDefault
public class IntesisBoxSocketApi {
private final Logger logger = LoggerFactory.getLogger(IntesisBoxSocketApi.class);
private final String ipAddress;
private final int port;
private final String readerThreadName;
private @Nullable IntesisSocket tcpSocket = null;
private @Nullable OutputStreamWriter tcpOutput = null;
private @Nullable BufferedReader tcpInput = null;
private @Nullable IntesisBoxChangeListener changeListener;
private boolean connected = false;
public IntesisBoxSocketApi(final String ipAddress, final int port, final String readerThreadName) {
this.ipAddress = ipAddress;
this.port = port;
this.readerThreadName = readerThreadName;
}
private class IntesisSocket {
final Socket socket;
public IntesisSocket() throws UnknownHostException, IOException {
socket = new Socket();
SocketAddress tcpSocketAddress = new InetSocketAddress(ipAddress, port);
socket.connect(tcpSocketAddress);
}
public void close() throws IOException {
socket.close();
}
}
public void openConnection() throws IOException {
closeConnection();
IntesisBoxChangeListener listener = this.changeListener;
IntesisSocket localSocket = tcpSocket = new IntesisSocket();
tcpOutput = new OutputStreamWriter(localSocket.socket.getOutputStream(), StandardCharsets.US_ASCII);
tcpInput = new BufferedReader(
new InputStreamReader(localSocket.socket.getInputStream(), StandardCharsets.US_ASCII));
Thread tcpListener = new Thread(new TCPListener());
tcpListener.setName(readerThreadName);
tcpListener.setDaemon(true);
tcpListener.start();
setConnected(true);
if (listener != null) {
listener.connectionStatusChanged(ThingStatus.ONLINE, null);
}
}
public void closeConnection() {
try {
IntesisSocket localSocket = tcpSocket;
OutputStreamWriter localOutput = tcpOutput;
BufferedReader localInput = tcpInput;
if (localSocket != null) {
localSocket.close();
localSocket = null;
}
if (localInput != null) {
localInput.close();
localInput = null;
}
if (localOutput != null) {
localOutput.close();
localOutput = null;
}
setConnected(false);
} catch (IOException ioException) {
logger.debug("closeConnection(): Unable to close connection - {}", ioException.getMessage());
} catch (Exception exception) {
logger.debug("closeConnection(): Error closing connection - {}", exception.getMessage());
}
}
private class TCPListener implements Runnable {
/**
* Run method. Runs the MessageListener thread
*/
@Override
public void run() {
while (isConnected()) {
String message = read();
readMessage(message);
}
}
}
public void addIntesisBoxChangeListener(IntesisBoxChangeListener listener) {
if (this.changeListener == null) {
this.changeListener = listener;
}
}
private void write(String data) {
IntesisBoxChangeListener listener = this.changeListener;
try {
OutputStreamWriter localOutput = tcpOutput;
if (localOutput != null) {
localOutput.write(data);
localOutput.flush();
}
} catch (IOException ioException) {
setConnected(false);
if (listener != null) {
listener.connectionStatusChanged(ThingStatus.OFFLINE, ioException.getMessage());
}
}
}
public String read() {
String message = "";
try {
BufferedReader localInput = tcpInput;
if (localInput != null) {
message = localInput.readLine();
}
} catch (IOException ioException) {
setConnected(false);
}
return message;
}
public void readMessage(String message) {
IntesisBoxChangeListener listener = this.changeListener;
if (listener != null && !message.isEmpty()) {
listener.messageReceived(message);
}
}
public void sendAlive() {
write("GET,1:*\r\n");
}
public void sendId() {
write("ID\r\n");
}
public void sendLimitsQuery() {
write("LIMITS:*\r\n");
}
public void sendCommand(String function, String value) {
String data = String.format("SET,1:%s,%s\r\n", function, value);
write(data);
}
public void sendQuery(String function) {
String data = String.format("GET,1:%s\r\n", function);
write(data);
}
public boolean isConnected() {
return this.connected;
}
public void setConnected(boolean connected) {
this.connected = connected;
}
public void removeIntesisBoxChangeListener(IntesisBoxHandler intesisBoxHandler) {
if (this.changeListener != null) {
this.changeListener = null;
}
}
}

View File

@ -23,7 +23,7 @@ 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.openhab.binding.intesis.internal.config.IntesisHomeConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -40,7 +40,7 @@ public class IntesisHomeHttpApi {
private final Logger logger = LoggerFactory.getLogger(IntesisHomeHttpApi.class);
private final HttpClient httpClient;
public IntesisHomeHttpApi(IntesisConfiguration config, HttpClient httpClient) {
public IntesisHomeHttpApi(IntesisHomeConfiguration config, HttpClient httpClient) {
this.httpClient = httpClient;
}

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.api;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link DataPointChangedEvent} is an event container for data point changes
*
* @author Hans-Jörg Merk - Initial contribution
*/
@NonNullByDefault
public class MessageReceivedEvent {
protected String message;
public MessageReceivedEvent(Object source, String message) {
this.message = message;
}
/**
* Gets the data-point of the event.
*
*/
@Nullable
public String getMessage() {
return this.message;
}
}

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.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link IntesisBoxConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Hans-Jörg Merk - Initial contribution
*/
@NonNullByDefault
public class IntesisBoxConfiguration {
public String ipAddress = "";
public int port;
}

View File

@ -10,17 +10,17 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.intesis.internal;
package org.openhab.binding.intesis.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link IntesisConfiguration} class contains fields mapping thing configuration parameters.
* The {@link IntesisHomeConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Hans-Jörg Merk - Initial contribution
*/
@NonNullByDefault
public class IntesisConfiguration {
public class IntesisHomeConfiguration {
public String ipAddress = "";
public String password = "";
}

View File

@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.intesis.internal;
package org.openhab.binding.intesis.internal.enums;
import org.eclipse.jdt.annotation.NonNullByDefault;

View File

@ -0,0 +1,394 @@
/**
* 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.binding.intesis.internal.api.IntesisBoxMessage.*;
import static org.openhab.core.thing.Thing.*;
import java.io.IOException;
import java.util.ArrayList;
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.stream.Collectors;
import javax.measure.quantity.Temperature;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.intesis.internal.IntesisDynamicStateDescriptionProvider;
import org.openhab.binding.intesis.internal.api.IntesisBoxChangeListener;
import org.openhab.binding.intesis.internal.api.IntesisBoxMessage;
import org.openhab.binding.intesis.internal.api.IntesisBoxSocketApi;
import org.openhab.binding.intesis.internal.config.IntesisBoxConfiguration;
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.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.StateOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link IntesisBoxHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Cody Cutrer - Initial contribution
* @author Rocky Amatulli - additions to include id message handling, dynamic channel options based on limits.
* @author Hans-Jörg Merk - refactored for openHAB 3.0 compatibility
*
*/
@NonNullByDefault
public class IntesisBoxHandler extends BaseThingHandler implements IntesisBoxChangeListener {
private final Logger logger = LoggerFactory.getLogger(IntesisBoxHandler.class);
private @Nullable IntesisBoxSocketApi intesisBoxSocketApi;
private final Map<String, String> properties = new HashMap<>();
private final Map<String, List<String>> limits = new HashMap<>();
private final IntesisDynamicStateDescriptionProvider intesisStateDescriptionProvider;
private IntesisBoxConfiguration config = new IntesisBoxConfiguration();
private double minTemp = 0.0, maxTemp = 0.0;
private boolean hasProperties = false;
private @Nullable ScheduledFuture<?> pollingTask;
public IntesisBoxHandler(Thing thing, IntesisDynamicStateDescriptionProvider intesisStateDescriptionProvider) {
super(thing);
this.intesisStateDescriptionProvider = intesisStateDescriptionProvider;
}
@Override
public void initialize() {
config = getConfigAs(IntesisBoxConfiguration.class);
if (!config.ipAddress.isEmpty()) {
updateStatus(ThingStatus.UNKNOWN);
scheduler.submit(() -> {
String readerThreadName = "OH-binding-" + getThing().getUID().getAsString();
IntesisBoxSocketApi intesisLocalApi = intesisBoxSocketApi = new IntesisBoxSocketApi(config.ipAddress,
config.port, readerThreadName);
intesisLocalApi.addIntesisBoxChangeListener(this);
try {
intesisLocalApi.openConnection();
intesisLocalApi.sendId();
intesisLocalApi.sendLimitsQuery();
intesisLocalApi.sendAlive();
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
return;
}
updateStatus(ThingStatus.ONLINE);
});
pollingTask = scheduler.scheduleWithFixedDelay(this::polling, 3, 45, TimeUnit.SECONDS);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No IP address specified)");
}
}
@Override
public void dispose() {
final ScheduledFuture<?> pollingTask = this.pollingTask;
IntesisBoxSocketApi api = this.intesisBoxSocketApi;
if (pollingTask != null) {
pollingTask.cancel(true);
this.pollingTask = null;
}
if (api != null) {
api.closeConnection();
api.removeIntesisBoxChangeListener(this);
}
super.dispose();
}
private synchronized void polling() {
IntesisBoxSocketApi api = this.intesisBoxSocketApi;
if (api != null) {
if (!api.isConnected()) {
try {
api.openConnection();
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
api.sendAlive();
api.sendId();
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
IntesisBoxSocketApi api = this.intesisBoxSocketApi;
if (api != null) {
if (!api.isConnected()) {
logger.trace("Sending command failed, not connected");
return;
}
if (command instanceof RefreshType) {
logger.trace("Refresh channel {}", channelUID.getId());
api.sendQuery(channelUID.getId());
return;
}
}
String value = "";
String function = "";
switch (channelUID.getId()) {
case CHANNEL_TYPE_POWER:
if (command instanceof OnOffType) {
function = "ONOFF";
value = command == OnOffType.ON ? "ON" : "OFF";
}
break;
case CHANNEL_TYPE_TARGETTEMP:
if (command instanceof QuantityType) {
QuantityType<?> celsiusTemperature = (QuantityType<?>) command;
celsiusTemperature = celsiusTemperature.toUnit(SIUnits.CELSIUS);
if (celsiusTemperature != null) {
double doubleValue = celsiusTemperature.doubleValue();
logger.trace("targetTemp double value = {}", doubleValue);
doubleValue = Math.max(minTemp, Math.min(maxTemp, doubleValue));
value = String.format("%.0f", doubleValue * 10);
function = "SETPTEMP";
logger.trace("targetTemp raw string = {}", value);
}
}
break;
case CHANNEL_TYPE_MODE:
function = "MODE";
value = command.toString();
break;
case CHANNEL_TYPE_FANSPEED:
function = "FANSP";
value = command.toString();
break;
case CHANNEL_TYPE_VANESUD:
function = "VANEUD";
value = command.toString();
break;
case CHANNEL_TYPE_VANESLR:
function = "VANELR";
value = command.toString();
break;
}
if (!value.isEmpty() || function.isEmpty()) {
if (api != null) {
logger.trace("Sending command {} to function {}", value, function);
api.sendCommand(function, value);
} else {
logger.warn("Sending command failed, could not get API");
}
}
}
private void populateProperties(String[] value) {
properties.put(PROPERTY_VENDOR, "Intesis");
properties.put(PROPERTY_MODEL_ID, value[0]);
properties.put(PROPERTY_MAC_ADDRESS, value[1]);
properties.put("ipAddress", value[2]);
properties.put("protocol", value[3]);
properties.put(PROPERTY_FIRMWARE_VERSION, value[4]);
properties.put("hostname", value[6]);
updateProperties(properties);
hasProperties = true;
}
private void receivedUpdate(String function, String receivedValue) {
String value = receivedValue;
logger.trace("receivedUpdate(): {} {}", function, value);
switch (function) {
case "ONOFF":
updateState(CHANNEL_TYPE_POWER, OnOffType.from(value));
break;
case "SETPTEMP":
if (value.equals("32768")) {
value = "0";
}
updateState(CHANNEL_TYPE_TARGETTEMP,
new QuantityType<Temperature>(Double.valueOf(value) / 10.0d, SIUnits.CELSIUS));
break;
case "AMBTEMP":
if (Double.valueOf(value).isNaN()) {
value = "0";
}
updateState(CHANNEL_TYPE_AMBIENTTEMP,
new QuantityType<Temperature>(Double.valueOf(value) / 10.0d, SIUnits.CELSIUS));
break;
case "MODE":
updateState(CHANNEL_TYPE_MODE, new StringType(value));
break;
case "FANSP":
updateState(CHANNEL_TYPE_FANSPEED, new StringType(value));
break;
case "VANEUD":
updateState(CHANNEL_TYPE_VANESUD, new StringType(value));
break;
case "VANELR":
updateState(CHANNEL_TYPE_VANESLR, new StringType(value));
break;
case "ERRCODE":
properties.put("errorCode", value);
updateProperties(properties);
break;
case "ERRSTATUS":
properties.put("errorStatus", value);
updateProperties(properties);
if ("ERR".equals(value)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"device reported an error");
}
break;
}
}
private void handleMessage(String data) {
logger.debug("handleMessage(): Message received - {}", data);
if (data.equals("ACK") || data.equals("")) {
return;
}
if (data.startsWith(ID + ':')) {
String[] value = data.substring(3).split(",");
if (!hasProperties) {
populateProperties(value);
}
DecimalType signalStrength = mapSignalStrength(Integer.parseInt(value[5]));
updateState(CHANNEL_TYPE_RSSI, signalStrength);
return;
}
IntesisBoxMessage message = IntesisBoxMessage.parse(data);
if (message != null) {
switch (message.getCommand()) {
case LIMITS:
logger.debug("handleMessage(): Limits received - {}", data);
String function = message.getFunction();
if (function.equals("SETPTEMP")) {
List<Double> limits = message.getLimitsValue().stream().map(l -> Double.valueOf(l) / 10.0d)
.collect(Collectors.toList());
if (limits.size() == 2) {
minTemp = limits.get(0);
maxTemp = limits.get(1);
}
logger.trace("Property target temperatures {} added", message.getValue());
properties.put("targetTemperature limits", "[" + minTemp + "," + maxTemp + "]");
addChannel(CHANNEL_TYPE_TARGETTEMP, "Number:Temperature");
} else {
switch (function) {
case "MODE":
properties.put("supported modes", message.getValue());
limits.put(CHANNEL_TYPE_MODE, message.getLimitsValue());
addChannel(CHANNEL_TYPE_MODE, "String");
break;
case "FANSP":
properties.put("supported fan levels", message.getValue());
limits.put(CHANNEL_TYPE_FANSPEED, message.getLimitsValue());
addChannel(CHANNEL_TYPE_FANSPEED, "String");
break;
case "VANEUD":
properties.put("supported vane up/down modes", message.getValue());
limits.put(CHANNEL_TYPE_VANESUD, message.getLimitsValue());
addChannel(CHANNEL_TYPE_VANESUD, "String");
break;
case "VANELR":
properties.put("supported vane left/right modes", message.getValue());
limits.put(CHANNEL_TYPE_VANESLR, message.getLimitsValue());
addChannel(CHANNEL_TYPE_VANESLR, "String");
break;
}
}
updateProperties(properties);
break;
case CHN:
receivedUpdate(message.getFunction(), message.getValue());
break;
}
}
}
public void addChannel(String channelId, String itemType) {
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 (limits.containsKey(channelId)) {
List<StateOption> options = new ArrayList<>();
for (String mode : limits.get(channelId)) {
options.add(new StateOption(mode,
mode.substring(0, 1).toUpperCase() + mode.substring(1).toLowerCase()));
}
intesisStateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), channelId),
options);
}
}
}
@Override
public void messageReceived(String messageLine) {
logger.trace("messageReceived() : {}", messageLine);
handleMessage(messageLine);
}
@Override
public void connectionStatusChanged(ThingStatus status, @Nullable String message) {
if (message != null) {
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
}
this.updateStatus(status);
}
public static DecimalType mapSignalStrength(int dbm) {
int strength = -1;
if (dbm > -60) {
strength = 4;
} else if (dbm > -70) {
strength = 3;
} else if (dbm > -80) {
strength = 2;
} else if (dbm > -90) {
strength = 1;
} else {
strength = 0;
}
return new DecimalType(strength);
}
}

View File

@ -29,10 +29,10 @@ 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.config.IntesisHomeConfiguration;
import org.openhab.binding.intesis.internal.enums.IntesisHomeModeEnum;
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;
@ -83,7 +83,7 @@ public class IntesisHomeHandler extends BaseThingHandler {
private final Gson gson = new Gson();
private IntesisConfiguration config = new IntesisConfiguration();
private IntesisHomeConfiguration config = new IntesisHomeConfiguration();
private @Nullable ScheduledFuture<?> refreshJob;
@ -97,7 +97,7 @@ public class IntesisHomeHandler extends BaseThingHandler {
@Override
public void initialize() {
updateStatus(ThingStatus.UNKNOWN);
config = getConfigAs(IntesisConfiguration.class);
config = getConfigAs(IntesisHomeConfiguration.class);
if (config.ipAddress.isEmpty() && config.password.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "IP-Address and password not set");
return;
@ -336,7 +336,7 @@ public class IntesisHomeHandler extends BaseThingHandler {
break;
}
}
properties.put("Supported modes", opModes.toString());
properties.put("supported modes", opModes.toString());
channelId = CHANNEL_TYPE_MODE;
addChannel(channelId, itemType, opModes);
break;
@ -349,7 +349,7 @@ public class IntesisHomeHandler extends BaseThingHandler {
fanLevels.add(fanString);
}
}
properties.put("Supported fan levels", fanLevels.toString());
properties.put("supported fan levels", fanLevels.toString());
channelId = CHANNEL_TYPE_FANSPEED;
addChannel(channelId, itemType, fanLevels);
break;
@ -372,12 +372,12 @@ public class IntesisHomeHandler extends BaseThingHandler {
switch (datapoint.uid) {
case 5:
channelId = CHANNEL_TYPE_VANESUD;
properties.put("Supported vane up/down modes", swingModes.toString());
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());
properties.put("supported vane left/right modes", swingModes.toString());
addChannel(channelId, itemType, swingModes);
break;
}

View File

@ -29,7 +29,7 @@ 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.label = Fan 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
@ -40,3 +40,7 @@ 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
channel-type.intesis.errorCode.label = Error Code
channel-type.intesis.errorCode.description = Shows the Air Conditioners error code if an error was found.
channel-type.intesis.errorStatus.label = Error Status
channel-type.intesis.errorStatus.description = Indicates if the Air Conditioner has encountered an error.

View File

@ -40,3 +40,8 @@ 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
channel-type.intesis.errorCode.label = Fehlercode
channel-type.intesis.errorCode.description = Zeigt im Fehlerzustand den Fehlercode an.
channel-type.intesis.errorStatus.label = Fehlerstatus
channel-type.intesis.errorStatus.description = Zeigt an, ob sich der Air Conditioner im Zustand "Fehler" befindet.

View File

@ -23,4 +23,48 @@
</parameter>
</config-description>
</thing-type>
<thing-type id="intesisBox">
<label>IntesisBox Adapter</label>
<description>Represents a single IntesisBox adapter on the network, connected to an A/C unit.</description>
<channels>
<channel id="power" typeId="system.power"/>
<channel id="wifiSignal" typeId="system.signal-strength"/>
<channel id="ambientTemperature" typeId="ambientTemperature"/>
<channel id="errorCode" typeId="errorCode"/>
<channel id="errorStatus" typeId="errorStatus"/>
</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="port" type="integer" required="true">
<label>Port</label>
<description>The TCP port to the IntesisBox.</description>
<default>3310</default>
</parameter>
</config-description>
</thing-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="errorCode">
<item-type>String</item-type>
<label>@text/channel-type.intesis.errorCode.label</label>
<description>@text/channel-type.intesis.errorCode.description</description>
</channel-type>
<channel-type id="errorStatus">
<item-type>String</item-type>
<label>@text/channel-type.intesis.errorStatus.label</label>
<description>@text/channel-type.intesis.errorStatus.description</description>
</channel-type>
</thing:thing-descriptions>