From a31398ddd43fa418670904b7fd12835274c88f59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans-J=C3=B6rg=20Merk?= Date: Thu, 8 Oct 2020 10:10:19 +0200 Subject: [PATCH 1/8] Intesis Binding - added IntesisBox support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hans-Jörg Merk --- bundles/org.openhab.binding.intesis/README.md | 52 +-- .../internal/IntesisConfiguration.java | 1 + .../api/IntesisBoxChangeListener.java | 36 ++ .../internal/api/IntesisBoxIdentity.java | 37 ++ .../internal/api/IntesisBoxMessage.java | 79 ++++ .../internal/api/IntesisBoxSocketApi.java | 232 +++++++++++ .../internal/api/MessageReceivedEvent.java | 39 ++ .../enums/IntesisBoxFunctionEnum.java | 40 ++ .../{ => enums}/IntesisHomeModeEnum.java | 2 +- .../internal/handler/IntesisBoxHandler.java | 371 ++++++++++++++++++ .../internal/handler/IntesisHomeHandler.java | 10 +- .../resources/OH-INF/i18n/intesis.properties | 2 +- .../resources/OH-INF/thing/thing-types.xml | 21 + 13 files changed, 890 insertions(+), 32 deletions(-) create mode 100644 bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxChangeListener.java create mode 100644 bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxIdentity.java create mode 100644 bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxMessage.java create mode 100644 bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java create mode 100644 bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/MessageReceivedEvent.java create mode 100644 bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/enums/IntesisBoxFunctionEnum.java rename bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/{ => enums}/IntesisHomeModeEnum.java (94%) create mode 100644 bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java diff --git a/bundles/org.openhab.binding.intesis/README.md b/bundles/org.openhab.binding.intesis/README.md index b77b6e9b7895..f16bfb21ff0c 100644 --- a/bundles/org.openhab.binding.intesis/README.md +++ b/bundles/org.openhab.binding.intesis/README.md @@ -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,37 @@ 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. | range between 18°C and 30°C | +| 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 +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 +57,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 +92,3 @@ sitemap intesishome label="My AC control" { } } ``` - diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisConfiguration.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisConfiguration.java index 31860257a440..3db6399d268d 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisConfiguration.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisConfiguration.java @@ -23,4 +23,5 @@ public class IntesisConfiguration { public String ipAddress = ""; public String password = ""; + public int port; } diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxChangeListener.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxChangeListener.java new file mode 100644 index 000000000000..cd63832ef92e --- /dev/null +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxChangeListener.java @@ -0,0 +1,36 @@ +/** + * 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.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); +} diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxIdentity.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxIdentity.java new file mode 100644 index 000000000000..fa404ed507b0 --- /dev/null +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxIdentity.java @@ -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 java.util.HashMap; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * @author Rocky Amatulli - Initial contribution + */ +@NonNullByDefault +public class IntesisBoxIdentity { + + public HashMap value = new HashMap(); + + public IntesisBoxIdentity(String data) { + String[] value = data.substring(3).split(","); + this.value.put("MODEL", value[0]); // The Intesis device model reference + this.value.put("MAC", value[1]); // The 6 bytes of the MAC address + this.value.put("IP", value[2]); // The IP address of the IntesisBox + this.value.put("PROTOCOL", value[3]); // The external protocol supported + this.value.put("VERSION", value[4]); // The firmware version running in the device + this.value.put("RSSI", value[5]); // The received Signal Strength Indication for the Wi-Fi + this.value.put("NAME", value[6]); // The host name of the IntesisBox + } +} diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxMessage.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxMessage.java new file mode 100644 index 000000000000..5175a3d342e9 --- /dev/null +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxMessage.java @@ -0,0 +1,79 @@ +/** + * 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( + "^(ID|INFO|SET|CHN|GET|LOGIN|LOGOUT|CFG|LIMITS)(?:,(\\d+))?:(APPVERSION|RUNVERSION|CFGVERSION|HASH|ONOFF|MODE|SETPTEMP|FANSP|VANEUD|VANELR|AMBTEMP|ERRSTATUS|ERRCODE),([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 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)); + } +} diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java new file mode 100644 index 000000000000..9c783c48028c --- /dev/null +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java @@ -0,0 +1,232 @@ +/** + * 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 org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +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 @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) { + this.ipAddress = ipAddress; + this.port = port; + } + + 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(); + } + } + + @SuppressWarnings("null") + public void openConnection() throws IOException { + closeConnection(); + + logger.debug("openConnection(): Connecting to IntesisBox "); + + IntesisBoxChangeListener listener = this.changeListener; + if (listener != null) { + logger.debug("IntesisBox listener added"); + } + + tcpSocket = new IntesisSocket(); + tcpOutput = new OutputStreamWriter(tcpSocket.socket.getOutputStream(), "US-ASCII"); + tcpInput = new BufferedReader(new InputStreamReader(tcpSocket.socket.getInputStream())); + + Thread tcpListener = new Thread(new TCPListener()); + tcpListener.start(); + + setConnected(true); + if (listener != null) { + listener.connectionStatusChanged(ThingStatus.ONLINE); + } + } + + @SuppressWarnings("null") + public void closeConnection() { + try { + if (tcpSocket != null) { + logger.debug("closeConnection(): Closing Socket!"); + tcpSocket.close(); + tcpSocket = null; + } + if (tcpInput != null) { + logger.debug("closeConnection(): Closing Output Writer!"); + tcpInput.close(); + tcpInput = null; + } + if (tcpOutput != null) { + logger.debug("closeConnection(): Closing Input Reader!"); + tcpOutput.close(); + tcpOutput = null; + } + + setConnected(false); + logger.debug("closeConnection(): Closed TCP Connection!"); + } 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 { + private final Logger logger = LoggerFactory.getLogger(TCPListener.class); + + /** + * Run method. Runs the MessageListener thread + */ + @Override + public void run() { + try { + while (isConnected()) { + String message = read(); + try { + logger.trace("Calling readMessage()"); + readMessage(message); + } catch (Exception e) { + logger.debug("TCPListener(): Message not handled by thing: {}", e.getMessage()); + closeConnection(); + } + } + } catch (Exception e) { + logger.debug("TCPListener(): Unable to read message: {} ", e.getMessage(), e); + closeConnection(); + } + } + } + + public void addIntesisBoxChangeListener(IntesisBoxChangeListener listener) { + if (this.changeListener == null) { + this.changeListener = listener; + } + } + + private void write(String data) { + try { + if (tcpOutput != null) { + tcpOutput.write(data); + } + if (tcpOutput != null) { + tcpOutput.flush(); + } + } catch (IOException ioException) { + logger.debug("write(): {}", ioException.getMessage()); + setConnected(false); + } catch (Exception exception) { + logger.debug("write(): Unable to write to socket: {} ", exception.getMessage(), exception); + setConnected(false); + } + } + + public String read() { + String message = ""; + try { + if (tcpInput != null) { + message = tcpInput.readLine(); + logger.debug("read(): Message Received: {}", message); + } + } catch (IOException ioException) { + logger.debug("read(): IO Exception: {}", ioException.getMessage()); + setConnected(false); + } catch (Exception exception) { + logger.debug("read(): Exception: {} ", exception.getMessage(), exception); + setConnected(false); + } + return message; + } + + public void readMessage(String message) { + IntesisBoxChangeListener listener = this.changeListener; + + if (listener != null && !message.isEmpty()) { + logger.trace("readMessage(): Inform listener with message: {}", message); + listener.messageReceived(message); + } + } + + public void sendAlive() { + write("GET,1:*\r\n"); + logger.trace("Keep alive sent"); + } + + public void sendId() { + write("ID\r\n"); + logger.trace("Id request sent"); + } + + public void sendLimitsQuery() { + write("LIMITS:*\r\n"); + logger.trace("Limits request sent"); + } + + public void sendCommand(String function, String value) { + String data = String.format("SET,1:%s,%s\r\n", function, value); + write(data); + logger.trace("sendCommand(): '{}' Command Sent - {}", function, value); + } + + public void sendQuery(String function) { + String data = String.format("GET,1:%s\r\n", function); + write(data); + logger.trace("sendQuery(): '{}' Command Sent", function); + } + + public boolean isConnected() { + return this.connected; + } + + public void setConnected(boolean connected) { + this.connected = connected; + } +} diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/MessageReceivedEvent.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/MessageReceivedEvent.java new file mode 100644 index 000000000000..aea77479b6f7 --- /dev/null +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/MessageReceivedEvent.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/enums/IntesisBoxFunctionEnum.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/enums/IntesisBoxFunctionEnum.java new file mode 100644 index 000000000000..1e852aeebd59 --- /dev/null +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/enums/IntesisBoxFunctionEnum.java @@ -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.intesis.internal.enums; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link IntesisBoxFunctionEnum) contains informations for translating channels into internally used functions. + * + * @author Hans-Jörg Merk - Initial contribution + */ +@NonNullByDefault +public enum IntesisBoxFunctionEnum { + power("ONOFF"), + targetTemperature("SETPTEMP"), + mode("MODE"), + fanSpeed("FANSP"), + vanesUpDown("VANEUD"), + vanesLeftRight("VANELR"); + + private final String function; + + private IntesisBoxFunctionEnum(String function) { + this.function = function; + } + + public String getFunction() { + return function; + } +} diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisHomeModeEnum.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/enums/IntesisHomeModeEnum.java similarity index 94% rename from bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisHomeModeEnum.java rename to bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/enums/IntesisHomeModeEnum.java index c63ddb65df9b..7aae91cecdaa 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisHomeModeEnum.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/enums/IntesisHomeModeEnum.java @@ -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; diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java new file mode 100644 index 000000000000..bcbbcb5b3861 --- /dev/null +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java @@ -0,0 +1,371 @@ +/** + * 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.IntesisConfiguration; +import org.openhab.binding.intesis.internal.IntesisDynamicStateDescriptionProvider; +import org.openhab.binding.intesis.internal.api.IntesisBoxChangeListener; +import org.openhab.binding.intesis.internal.api.IntesisBoxIdentity; +import org.openhab.binding.intesis.internal.api.IntesisBoxMessage; +import org.openhab.binding.intesis.internal.api.IntesisBoxSocketApi; +import org.openhab.binding.intesis.internal.enums.IntesisBoxFunctionEnum; +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 properties = new HashMap<>(); + private final Map> limits = new HashMap>(); + + private final IntesisDynamicStateDescriptionProvider intesisStateDescriptionProvider; + + private IntesisConfiguration config = new IntesisConfiguration(); + + private double minTemp = 0.0d, maxTemp = 0.0d; + + private @Nullable ScheduledFuture pollingTask; + + public IntesisBoxHandler(Thing thing, IntesisDynamicStateDescriptionProvider intesisStateDescriptionProvider) { + super(thing); + this.intesisStateDescriptionProvider = intesisStateDescriptionProvider; + } + + @Override + public void initialize() { + config = getConfigAs(IntesisConfiguration.class); + + if (!config.ipAddress.isEmpty()) { + intesisBoxSocketApi = new IntesisBoxSocketApi(config.ipAddress, config.port); + intesisBoxSocketApi.addIntesisBoxChangeListener(this); + + updateStatus(ThingStatus.UNKNOWN); + scheduler.submit(() -> { + try { + intesisBoxSocketApi.openConnection(); + intesisBoxSocketApi.sendId(); + intesisBoxSocketApi.sendLimitsQuery(); + intesisBoxSocketApi.sendAlive(); + } catch (IOException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } + pollingTask = scheduler.scheduleWithFixedDelay(this::polling, 3, 45, TimeUnit.SECONDS); + updateStatus(ThingStatus.ONLINE); + }); + } 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(); + } + 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(); + ; + } + } + + @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.warn("Refresh channel {}", channelUID.getId()); + api.sendQuery(channelUID.getId()); + return; + } + } + String value = ""; + switch (channelUID.getId()) { + case CHANNEL_TYPE_POWER: + if (command instanceof OnOffType) { + value = ((OnOffType) 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(); + if (doubleValue < minTemp) { + doubleValue = minTemp; + } else if (doubleValue > maxTemp) { + doubleValue = maxTemp; + } + value = String.valueOf((int) Math.round(doubleValue * 10)); + } + } + break; + case CHANNEL_TYPE_MODE: + case CHANNEL_TYPE_FANSPEED: + case CHANNEL_TYPE_VANESUD: + case CHANNEL_TYPE_VANESLR: + value = command.toString(); + break; + } + if (!value.isEmpty()) { + String function = IntesisBoxFunctionEnum.valueOf(channelUID.getId()).getFunction(); + if (api != null) { + logger.trace("Sending command {} to function {}", value, function); + api.sendCommand(function, value); + } else { + logger.trace("Sending command failed, could not get API"); + } + } + } + + private void receivedId(String function, String value) { + logger.trace("receivedID(): {} {}", function, value); + properties.put(PROPERTY_VENDOR, "Intesis"); + switch (function) { + case "MODEL": + properties.put(PROPERTY_MODEL_ID, value); + break; + case "MAC": + properties.put(PROPERTY_MAC_ADDRESS, value); + break; + case "IP": + properties.put("ipAddress", value); + case "PROTOCOL": + properties.put("protocol", value); + break; + case "VERSION": + properties.put(PROPERTY_FIRMWARE_VERSION, value); + break; + case "RSSI": + properties.put("rssi", value); + break; + case "NAME": + properties.put("hostname", value); + break; + } + } + + private void receivedUpdate(String function, String value) { + 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(Double.valueOf(value) / 10.0d, SIUnits.CELSIUS)); + break; + case "AMBTEMP": + if (Double.valueOf(value).isNaN()) { + value = "0"; + } + updateState(CHANNEL_TYPE_AMBIENTTEMP, + new QuantityType(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; + } + } + + @SuppressWarnings("null") + private void handleMessage(String data) { + logger.trace("handleMessage(): Message received - {}", data); + if (data.equals("ACK") || data.equals("")) { + return; + } + if (data.startsWith(ID + ':')) { + synchronized (this) { + new IntesisBoxIdentity(data).value.forEach((name, value) -> receivedId(name, value)); + return; + } + } + IntesisBoxMessage message = IntesisBoxMessage.parse(data); + switch (message.getCommand()) { + case LIMITS: + logger.trace("handleMessage(): Limits received - {}", data); + synchronized (this) { + String function = message.getFunction(); + if (function.equals("SETPTEMP")) { + List 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; + } + } + addChannel(CHANNEL_TYPE_AMBIENTTEMP, "Number:Temperature"); + + } + 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 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) { + this.updateStatus(status); + } +} diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisHomeHandler.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisHomeHandler.java index 9dbb2e26a3a9..5c0904105b00 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisHomeHandler.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisHomeHandler.java @@ -31,8 +31,8 @@ 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.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; @@ -336,7 +336,7 @@ private void handleDataPointsResponse(Response response) { 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 @@ private void handleDataPointsResponse(Response response) { 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 @@ private void handleDataPointsResponse(Response response) { 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; } diff --git a/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/i18n/intesis.properties b/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/i18n/intesis.properties index cf808f1770fe..4ee2fd4a4d87 100644 --- a/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/i18n/intesis.properties +++ b/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/i18n/intesis.properties @@ -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 diff --git a/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/thing-types.xml index 254052635191..736a078ecdad 100644 --- a/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/thing-types.xml @@ -23,4 +23,25 @@ + + + + Represents a single IntesisBox adapter on the network, connected to an A/C unit. + + + + + + + @text/thing-type.config.intesis.ipAddress.description + network-address + + + + The TCP port to the IntesisBox. + 3310 + + + + From 8ea1d268bbd9b48e69875641212d0bc824aba056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans-J=C3=B6rg=20Merk?= Date: Thu, 8 Oct 2020 10:48:30 +0200 Subject: [PATCH 2/8] applied spotless MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hans-Jörg Merk --- .../resources/OH-INF/thing/thing-types.xml | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/thing-types.xml index 736a078ecdad..d57a816032ff 100644 --- a/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/thing-types.xml @@ -24,24 +24,24 @@ - - - Represents a single IntesisBox adapter on the network, connected to an A/C unit. - - - - - - - @text/thing-type.config.intesis.ipAddress.description - network-address - - - - The TCP port to the IntesisBox. - 3310 - - - + + + Represents a single IntesisBox adapter on the network, connected to an A/C unit. + + + + + + + @text/thing-type.config.intesis.ipAddress.description + network-address + + + + The TCP port to the IntesisBox. + 3310 + + + From b255eec678d0820e0a7f3d19899fb83b0917116b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans-J=C3=B6rg=20Merk?= Date: Mon, 19 Oct 2020 16:49:02 +0200 Subject: [PATCH 3/8] Changes after first review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hans-Jörg Merk --- bundles/org.openhab.binding.intesis/README.md | 20 +-- .../api/IntesisBoxChangeListener.java | 3 +- .../internal/api/IntesisBoxIdentity.java | 2 +- .../internal/api/IntesisBoxSocketApi.java | 55 ++----- .../internal/api/IntesisHomeHttpApi.java | 4 +- .../config/IntesisBoxConfiguration.java | 26 ++++ .../IntesisHomeConfiguration.java} | 7 +- .../internal/handler/IntesisBoxHandler.java | 146 ++++++++++-------- .../internal/handler/IntesisHomeHandler.java | 6 +- 9 files changed, 137 insertions(+), 132 deletions(-) create mode 100644 bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/config/IntesisBoxConfiguration.java rename bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/{IntesisConfiguration.java => config/IntesisHomeConfiguration.java} (74%) diff --git a/bundles/org.openhab.binding.intesis/README.md b/bundles/org.openhab.binding.intesis/README.md index f16bfb21ff0c..c74d4eeb7814 100644 --- a/bundles/org.openhab.binding.intesis/README.md +++ b/bundles/org.openhab.binding.intesis/README.md @@ -30,16 +30,16 @@ The binding uses the following configuration parameters. ## 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. | range between 18°C and 30°C | -| 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) | | Note that individual A/C units may not support all channels, or all possible values for those channels. diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxChangeListener.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxChangeListener.java index cd63832ef92e..a61ad69c2162 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxChangeListener.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxChangeListener.java @@ -13,6 +13,7 @@ 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; /** @@ -32,5 +33,5 @@ public interface IntesisBoxChangeListener { * This method will be called in case the connection status has changed. * */ - void connectionStatusChanged(ThingStatus status); + void connectionStatusChanged(ThingStatus status, @Nullable String message); } diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxIdentity.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxIdentity.java index fa404ed507b0..227165b97986 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxIdentity.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxIdentity.java @@ -22,7 +22,7 @@ @NonNullByDefault public class IntesisBoxIdentity { - public HashMap value = new HashMap(); + public HashMap value = new HashMap<>(); public IntesisBoxIdentity(String data) { String[] value = data.substring(3).split(","); diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java index 9c783c48028c..400f6a8d70ee 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java @@ -68,51 +68,38 @@ public void close() throws IOException { } } - @SuppressWarnings("null") public void openConnection() throws IOException { closeConnection(); - logger.debug("openConnection(): Connecting to IntesisBox "); - IntesisBoxChangeListener listener = this.changeListener; - if (listener != null) { - logger.debug("IntesisBox listener added"); - } - - tcpSocket = new IntesisSocket(); - tcpOutput = new OutputStreamWriter(tcpSocket.socket.getOutputStream(), "US-ASCII"); - tcpInput = new BufferedReader(new InputStreamReader(tcpSocket.socket.getInputStream())); + IntesisSocket localSocket = tcpSocket = new IntesisSocket(); + tcpOutput = new OutputStreamWriter(localSocket.socket.getOutputStream(), "US-ASCII"); + tcpInput = new BufferedReader(new InputStreamReader(localSocket.socket.getInputStream())); Thread tcpListener = new Thread(new TCPListener()); tcpListener.start(); setConnected(true); if (listener != null) { - listener.connectionStatusChanged(ThingStatus.ONLINE); + listener.connectionStatusChanged(ThingStatus.ONLINE, null); } } - @SuppressWarnings("null") public void closeConnection() { try { if (tcpSocket != null) { - logger.debug("closeConnection(): Closing Socket!"); tcpSocket.close(); tcpSocket = null; } if (tcpInput != null) { - logger.debug("closeConnection(): Closing Output Writer!"); tcpInput.close(); tcpInput = null; } if (tcpOutput != null) { - logger.debug("closeConnection(): Closing Input Reader!"); tcpOutput.close(); tcpOutput = null; } - setConnected(false); - logger.debug("closeConnection(): Closed TCP Connection!"); } catch (IOException ioException) { logger.debug("closeConnection(): Unable to close connection - {}", ioException.getMessage()); } catch (Exception exception) { @@ -121,27 +108,15 @@ public void closeConnection() { } private class TCPListener implements Runnable { - private final Logger logger = LoggerFactory.getLogger(TCPListener.class); /** * Run method. Runs the MessageListener thread */ @Override public void run() { - try { - while (isConnected()) { - String message = read(); - try { - logger.trace("Calling readMessage()"); - readMessage(message); - } catch (Exception e) { - logger.debug("TCPListener(): Message not handled by thing: {}", e.getMessage()); - closeConnection(); - } - } - } catch (Exception e) { - logger.debug("TCPListener(): Unable to read message: {} ", e.getMessage(), e); - closeConnection(); + while (isConnected()) { + String message = read(); + readMessage(message); } } } @@ -153,6 +128,7 @@ public void addIntesisBoxChangeListener(IntesisBoxChangeListener listener) { } private void write(String data) { + IntesisBoxChangeListener listener = this.changeListener; try { if (tcpOutput != null) { tcpOutput.write(data); @@ -163,9 +139,9 @@ private void write(String data) { } catch (IOException ioException) { logger.debug("write(): {}", ioException.getMessage()); setConnected(false); - } catch (Exception exception) { - logger.debug("write(): Unable to write to socket: {} ", exception.getMessage(), exception); - setConnected(false); + if (listener != null) { + listener.connectionStatusChanged(ThingStatus.OFFLINE, ioException.getMessage()); + } } } @@ -179,9 +155,6 @@ public String read() { } catch (IOException ioException) { logger.debug("read(): IO Exception: {}", ioException.getMessage()); setConnected(false); - } catch (Exception exception) { - logger.debug("read(): Exception: {} ", exception.getMessage(), exception); - setConnected(false); } return message; } @@ -190,36 +163,30 @@ public void readMessage(String message) { IntesisBoxChangeListener listener = this.changeListener; if (listener != null && !message.isEmpty()) { - logger.trace("readMessage(): Inform listener with message: {}", message); listener.messageReceived(message); } } public void sendAlive() { write("GET,1:*\r\n"); - logger.trace("Keep alive sent"); } public void sendId() { write("ID\r\n"); - logger.trace("Id request sent"); } public void sendLimitsQuery() { write("LIMITS:*\r\n"); - logger.trace("Limits request sent"); } public void sendCommand(String function, String value) { String data = String.format("SET,1:%s,%s\r\n", function, value); write(data); - logger.trace("sendCommand(): '{}' Command Sent - {}", function, value); } public void sendQuery(String function) { String data = String.format("GET,1:%s\r\n", function); write(data); - logger.trace("sendQuery(): '{}' Command Sent", function); } public boolean isConnected() { diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisHomeHttpApi.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisHomeHttpApi.java index 7bbeb095b186..5880234a3a1e 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisHomeHttpApi.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisHomeHttpApi.java @@ -23,7 +23,7 @@ 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; } diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/config/IntesisBoxConfiguration.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/config/IntesisBoxConfiguration.java new file mode 100644 index 000000000000..21ec814e0303 --- /dev/null +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/config/IntesisBoxConfiguration.java @@ -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; +} diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisConfiguration.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/config/IntesisHomeConfiguration.java similarity index 74% rename from bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisConfiguration.java rename to bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/config/IntesisHomeConfiguration.java index 3db6399d268d..58fa954720c2 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisConfiguration.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/config/IntesisHomeConfiguration.java @@ -10,18 +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 = ""; - public int port; } diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java index bcbbcb5b3861..87f2afd16810 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java @@ -29,12 +29,12 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.intesis.internal.IntesisConfiguration; import org.openhab.binding.intesis.internal.IntesisDynamicStateDescriptionProvider; import org.openhab.binding.intesis.internal.api.IntesisBoxChangeListener; import org.openhab.binding.intesis.internal.api.IntesisBoxIdentity; 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.binding.intesis.internal.enums.IntesisBoxFunctionEnum; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.QuantityType; @@ -76,9 +76,9 @@ public class IntesisBoxHandler extends BaseThingHandler implements IntesisBoxCha private final IntesisDynamicStateDescriptionProvider intesisStateDescriptionProvider; - private IntesisConfiguration config = new IntesisConfiguration(); + private IntesisBoxConfiguration config = new IntesisBoxConfiguration(); - private double minTemp = 0.0d, maxTemp = 0.0d; + private double minTemp = 0.0, maxTemp = 0.0; private @Nullable ScheduledFuture pollingTask; @@ -89,7 +89,7 @@ public IntesisBoxHandler(Thing thing, IntesisDynamicStateDescriptionProvider int @Override public void initialize() { - config = getConfigAs(IntesisConfiguration.class); + config = getConfigAs(IntesisBoxConfiguration.class); if (!config.ipAddress.isEmpty()) { intesisBoxSocketApi = new IntesisBoxSocketApi(config.ipAddress, config.port); @@ -98,10 +98,18 @@ public void initialize() { updateStatus(ThingStatus.UNKNOWN); scheduler.submit(() -> { try { - intesisBoxSocketApi.openConnection(); - intesisBoxSocketApi.sendId(); - intesisBoxSocketApi.sendLimitsQuery(); - intesisBoxSocketApi.sendAlive(); + if (intesisBoxSocketApi != null) { + intesisBoxSocketApi.openConnection(); + } + if (intesisBoxSocketApi != null) { + intesisBoxSocketApi.sendId(); + } + if (intesisBoxSocketApi != null) { + intesisBoxSocketApi.sendLimitsQuery(); + } + if (intesisBoxSocketApi != null) { + intesisBoxSocketApi.sendAlive(); + } } catch (IOException e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } @@ -140,7 +148,6 @@ private synchronized void polling() { } } api.sendAlive(); - ; } } @@ -153,7 +160,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { return; } if (command instanceof RefreshType) { - logger.warn("Refresh channel {}", channelUID.getId()); + logger.trace("Refresh channel {}", channelUID.getId()); api.sendQuery(channelUID.getId()); return; } @@ -162,7 +169,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { switch (channelUID.getId()) { case CHANNEL_TYPE_POWER: if (command instanceof OnOffType) { - value = ((OnOffType) command == OnOffType.ON) ? "ON" : "OFF"; + value = command == OnOffType.ON ? "ON" : "OFF"; } break; case CHANNEL_TYPE_TARGETTEMP: @@ -171,12 +178,10 @@ public void handleCommand(ChannelUID channelUID, Command command) { celsiusTemperature = celsiusTemperature.toUnit(SIUnits.CELSIUS); if (celsiusTemperature != null) { double doubleValue = celsiusTemperature.doubleValue(); - if (doubleValue < minTemp) { - doubleValue = minTemp; - } else if (doubleValue > maxTemp) { - doubleValue = maxTemp; - } - value = String.valueOf((int) Math.round(doubleValue * 10)); + logger.trace("targetTemp double value = {}", doubleValue); + doubleValue = Math.max(minTemp, Math.min(maxTemp, doubleValue)); + value = String.format("%.0f", doubleValue * 10); + logger.trace("targetTemp raw string = {}", value); } } break; @@ -193,7 +198,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { logger.trace("Sending command {} to function {}", value, function); api.sendCommand(function, value); } else { - logger.trace("Sending command failed, could not get API"); + logger.warn("Sending command failed, could not get API"); } } } @@ -223,9 +228,11 @@ private void receivedId(String function, String value) { properties.put("hostname", value); break; } + logger.trace("Update Properties for ID : {}", properties); } - private void receivedUpdate(String function, String value) { + private void receivedUpdate(String function, String receivedValue) { + String value = receivedValue; logger.trace("receivedUpdate(): {} {}", function, value); switch (function) { case "ONOFF": @@ -275,7 +282,7 @@ private void receivedUpdate(String function, String value) { @SuppressWarnings("null") private void handleMessage(String data) { - logger.trace("handleMessage(): Message received - {}", data); + logger.debug("handleMessage(): Message received - {}", data); if (data.equals("ACK") || data.equals("")) { return; } @@ -286,53 +293,55 @@ private void handleMessage(String data) { } } IntesisBoxMessage message = IntesisBoxMessage.parse(data); - switch (message.getCommand()) { - case LIMITS: - logger.trace("handleMessage(): Limits received - {}", data); - synchronized (this) { - String function = message.getFunction(); - if (function.equals("SETPTEMP")) { - List 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; + if (message.getCommand() != null) { + switch (message.getCommand()) { + case LIMITS: + logger.debug("handleMessage(): Limits received - {}", data); + synchronized (this) { + String function = message.getFunction(); + if (function.equals("SETPTEMP")) { + List 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; + } } - } - addChannel(CHANNEL_TYPE_AMBIENTTEMP, "Number:Temperature"); + addChannel(CHANNEL_TYPE_AMBIENTTEMP, "Number:Temperature"); - } - updateProperties(properties); - break; - case CHN: - receivedUpdate(message.getFunction(), message.getValue()); - break; + } + updateProperties(properties); + break; + case CHN: + receivedUpdate(message.getFunction(), message.getValue()); + break; + } } } @@ -350,7 +359,7 @@ public void addChannel(String channelId, String itemType) { List options = new ArrayList<>(); for (String mode : limits.get(channelId)) { options.add(new StateOption(mode, - (mode.substring(0, 1).toUpperCase() + mode.substring(1).toLowerCase()))); + mode.substring(0, 1).toUpperCase() + mode.substring(1).toLowerCase())); } intesisStateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), channelId), options); @@ -365,7 +374,10 @@ public void messageReceived(String messageLine) { } @Override - public void connectionStatusChanged(ThingStatus status) { + public void connectionStatusChanged(ThingStatus status, @Nullable String message) { + if (message != null) { + this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message); + } this.updateStatus(status); } } diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisHomeHandler.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisHomeHandler.java index 5c0904105b00..194a50db15c6 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisHomeHandler.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisHomeHandler.java @@ -29,9 +29,9 @@ 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.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; @@ -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 IntesisHomeHandler(final Thing thing, final HttpClient httpClient, @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; From bf96a6ffe163d8fa1843ef8ec16f7cae1b84c2c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans-J=C3=B6rg=20Merk?= Date: Wed, 21 Oct 2020 09:50:42 +0200 Subject: [PATCH 4/8] Changes after second review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hans-Jörg Merk --- bundles/org.openhab.binding.intesis/README.md | 4 +- .../internal/IntesisBindingConstants.java | 3 + .../internal/IntesisHandlerFactory.java | 13 +- .../internal/api/IntesisBoxIdentity.java | 37 ----- .../internal/api/IntesisBoxMessage.java | 3 +- .../internal/api/IntesisBoxSocketApi.java | 5 +- .../enums/IntesisBoxFunctionEnum.java | 40 ----- .../internal/handler/IntesisBoxHandler.java | 137 +++++++++--------- .../resources/OH-INF/i18n/intesis.properties | 4 + .../OH-INF/i18n/intesis_de.properties | 5 + .../OH-INF/thing/dynamic-channels.xml | 10 ++ 11 files changed, 105 insertions(+), 156 deletions(-) delete mode 100644 bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxIdentity.java delete mode 100644 bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/enums/IntesisBoxFunctionEnum.java diff --git a/bundles/org.openhab.binding.intesis/README.md b/bundles/org.openhab.binding.intesis/README.md index c74d4eeb7814..6ecfbf743ce3 100644 --- a/bundles/org.openhab.binding.intesis/README.md +++ b/bundles/org.openhab.binding.intesis/README.md @@ -32,7 +32,7 @@ The binding uses the following configuration parameters. | Channel ID | Item Type | Description | Possible Values | |--------------------|--------------------|--------------------------------------------------------|-----------------------------| -| power | Switch | Turns power on/off for your climate system. | ON, OFF | +| 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 | @@ -40,6 +40,8 @@ The binding uses the following configuration parameters. | 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 | Note that individual A/C units may not support all channels, or all possible values for those channels. diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisBindingConstants.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisBindingConstants.java index e90a8178db56..783b1511369c 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisBindingConstants.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisBindingConstants.java @@ -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,6 @@ 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"; } diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisHandlerFactory.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisHandlerFactory.java index 1bee55fad4ff..72c4c171aece 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisHandlerFactory.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisHandlerFactory.java @@ -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 SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_INTESISHOME); + private static final Set 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, @@ -73,6 +77,11 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { return new IntesisHomeHandler(thing, httpClient, intesisStateDescriptionProvider); } + if (THING_TYPE_INTESISBOX.equals(thingTypeUID)) { + logger.debug("Creating a IntesisBoxHandler for thing '{}'", thing.getUID()); + return new IntesisBoxHandler(thing, intesisStateDescriptionProvider); + } + return null; } } diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxIdentity.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxIdentity.java deleted file mode 100644 index 227165b97986..000000000000 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxIdentity.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * 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.HashMap; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * @author Rocky Amatulli - Initial contribution - */ -@NonNullByDefault -public class IntesisBoxIdentity { - - public HashMap value = new HashMap<>(); - - public IntesisBoxIdentity(String data) { - String[] value = data.substring(3).split(","); - this.value.put("MODEL", value[0]); // The Intesis device model reference - this.value.put("MAC", value[1]); // The 6 bytes of the MAC address - this.value.put("IP", value[2]); // The IP address of the IntesisBox - this.value.put("PROTOCOL", value[3]); // The external protocol supported - this.value.put("VERSION", value[4]); // The firmware version running in the device - this.value.put("RSSI", value[5]); // The received Signal Strength Indication for the Wi-Fi - this.value.put("NAME", value[6]); // The host name of the IntesisBox - } -} diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxMessage.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxMessage.java index 5175a3d342e9..cfe26429e18c 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxMessage.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxMessage.java @@ -36,8 +36,7 @@ public class IntesisBoxMessage { public static final String LIMITS = "LIMITS"; public static final String DISCOVER = "DISCOVER"; - private static final Pattern REGEX = Pattern.compile( - "^(ID|INFO|SET|CHN|GET|LOGIN|LOGOUT|CFG|LIMITS)(?:,(\\d+))?:(APPVERSION|RUNVERSION|CFGVERSION|HASH|ONOFF|MODE|SETPTEMP|FANSP|VANEUD|VANELR|AMBTEMP|ERRSTATUS|ERRCODE),([A-Z0-9.,\\[\\]]+)$"); + private static final Pattern REGEX = Pattern.compile("^([^,]+)(?:,(\\d+))?:([^,]+),([A-Z0-9.,\\[\\]]+)$"); @SuppressWarnings("unused") private final String acNum; diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java index 400f6a8d70ee..9a5a428986af 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java @@ -74,9 +74,11 @@ public void openConnection() throws IOException { IntesisBoxChangeListener listener = this.changeListener; IntesisSocket localSocket = tcpSocket = new IntesisSocket(); tcpOutput = new OutputStreamWriter(localSocket.socket.getOutputStream(), "US-ASCII"); - tcpInput = new BufferedReader(new InputStreamReader(localSocket.socket.getInputStream())); + tcpInput = new BufferedReader(new InputStreamReader(localSocket.socket.getInputStream(), "US-ASCII")); Thread tcpListener = new Thread(new TCPListener()); + tcpListener.setName("IntesisBoxTCPListener"); + tcpListener.setDaemon(true); tcpListener.start(); setConnected(true); @@ -134,7 +136,6 @@ private void write(String data) { tcpOutput.write(data); } if (tcpOutput != null) { - tcpOutput.flush(); } } catch (IOException ioException) { logger.debug("write(): {}", ioException.getMessage()); diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/enums/IntesisBoxFunctionEnum.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/enums/IntesisBoxFunctionEnum.java deleted file mode 100644 index 1e852aeebd59..000000000000 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/enums/IntesisBoxFunctionEnum.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * 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.enums; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link IntesisBoxFunctionEnum) contains informations for translating channels into internally used functions. - * - * @author Hans-Jörg Merk - Initial contribution - */ -@NonNullByDefault -public enum IntesisBoxFunctionEnum { - power("ONOFF"), - targetTemperature("SETPTEMP"), - mode("MODE"), - fanSpeed("FANSP"), - vanesUpDown("VANEUD"), - vanesLeftRight("VANELR"); - - private final String function; - - private IntesisBoxFunctionEnum(String function) { - this.function = function; - } - - public String getFunction() { - return function; - } -} diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java index 87f2afd16810..24dd736e9b21 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java @@ -31,11 +31,9 @@ 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.IntesisBoxIdentity; 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.binding.intesis.internal.enums.IntesisBoxFunctionEnum; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.StringType; @@ -92,11 +90,15 @@ public void initialize() { config = getConfigAs(IntesisBoxConfiguration.class); if (!config.ipAddress.isEmpty()) { - intesisBoxSocketApi = new IntesisBoxSocketApi(config.ipAddress, config.port); - intesisBoxSocketApi.addIntesisBoxChangeListener(this); updateStatus(ThingStatus.UNKNOWN); scheduler.submit(() -> { + addChannel(CHANNEL_TYPE_AMBIENTTEMP, "Number:Temperature"); + addChannel(CHANNEL_TYPE_ERRORCODE, "String"); + addChannel(CHANNEL_TYPE_ERRORSTATUS, "String"); + + intesisBoxSocketApi = new IntesisBoxSocketApi(config.ipAddress, config.port); + intesisBoxSocketApi.addIntesisBoxChangeListener(this); try { if (intesisBoxSocketApi != null) { intesisBoxSocketApi.openConnection(); @@ -166,9 +168,11 @@ public void handleCommand(ChannelUID channelUID, Command command) { } } String value = ""; + String function = ""; switch (channelUID.getId()) { case CHANNEL_TYPE_POWER: if (command instanceof OnOffType) { + function = "ONOFF"; value = command == OnOffType.ON ? "ON" : "OFF"; } break; @@ -181,19 +185,29 @@ public void handleCommand(ChannelUID channelUID, Command command) { 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()) { - String function = IntesisBoxFunctionEnum.valueOf(channelUID.getId()).getFunction(); + if (!value.isEmpty() || function.isEmpty()) { if (api != null) { logger.trace("Sending command {} to function {}", value, function); api.sendCommand(function, value); @@ -203,32 +217,17 @@ public void handleCommand(ChannelUID channelUID, Command command) { } } - private void receivedId(String function, String value) { - logger.trace("receivedID(): {} {}", function, value); + private void populateProperties(String data) { + String[] value = data.substring(3).split(","); properties.put(PROPERTY_VENDOR, "Intesis"); - switch (function) { - case "MODEL": - properties.put(PROPERTY_MODEL_ID, value); - break; - case "MAC": - properties.put(PROPERTY_MAC_ADDRESS, value); - break; - case "IP": - properties.put("ipAddress", value); - case "PROTOCOL": - properties.put("protocol", value); - break; - case "VERSION": - properties.put(PROPERTY_FIRMWARE_VERSION, value); - break; - case "RSSI": - properties.put("rssi", value); - break; - case "NAME": - properties.put("hostname", value); - break; - } - logger.trace("Update Properties for ID : {}", properties); + 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("rssi", value[5]); + properties.put("hostname", value[6]); + updateProperties(properties); } private void receivedUpdate(String function, String receivedValue) { @@ -287,54 +286,48 @@ private void handleMessage(String data) { return; } if (data.startsWith(ID + ':')) { - synchronized (this) { - new IntesisBoxIdentity(data).value.forEach((name, value) -> receivedId(name, value)); - return; - } + populateProperties(data); + return; } IntesisBoxMessage message = IntesisBoxMessage.parse(data); if (message.getCommand() != null) { switch (message.getCommand()) { case LIMITS: logger.debug("handleMessage(): Limits received - {}", data); - synchronized (this) { - String function = message.getFunction(); - if (function.equals("SETPTEMP")) { - List 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; - } + String function = message.getFunction(); + if (function.equals("SETPTEMP")) { + List 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; } - addChannel(CHANNEL_TYPE_AMBIENTTEMP, "Number:Temperature"); - } updateProperties(properties); break; diff --git a/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/i18n/intesis.properties b/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/i18n/intesis.properties index 4ee2fd4a4d87..2a04e56a09a7 100644 --- a/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/i18n/intesis.properties +++ b/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/i18n/intesis.properties @@ -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. diff --git a/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/i18n/intesis_de.properties b/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/i18n/intesis_de.properties index de2be2f9cda9..323f31bb55ce 100644 --- a/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/i18n/intesis_de.properties +++ b/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/i18n/intesis_de.properties @@ -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. + diff --git a/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/dynamic-channels.xml b/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/dynamic-channels.xml index 89e9a832f6f2..0615c7c75db0 100644 --- a/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/dynamic-channels.xml +++ b/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/dynamic-channels.xml @@ -42,5 +42,15 @@ @text/channel-type.intesis.outdoorTemperature.description + + String + + @text/channel-type.intesis.errorCode.description + + + String + + @text/channel-type.intesis.errorStatus.description + From 5ede91867bed3cd5b6f32bfb2f060c28e924fad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans-J=C3=B6rg=20Merk?= Date: Thu, 22 Oct 2020 09:26:55 +0200 Subject: [PATCH 5/8] Fix compiler warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hans-Jörg Merk --- .../internal/api/IntesisBoxSocketApi.java | 39 ++++++++++--------- .../internal/handler/IntesisBoxHandler.java | 20 ++++------ 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java index 9a5a428986af..f1bf2ad18efc 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java @@ -89,17 +89,21 @@ public void openConnection() throws IOException { public void closeConnection() { try { - if (tcpSocket != null) { - tcpSocket.close(); - tcpSocket = null; + IntesisSocket localSocket = tcpSocket; + OutputStreamWriter localOutput = tcpOutput; + BufferedReader localInput = tcpInput; + + if (localSocket != null) { + localSocket.close(); + localSocket = null; } - if (tcpInput != null) { - tcpInput.close(); - tcpInput = null; + if (localInput != null) { + localInput.close(); + localInput = null; } - if (tcpOutput != null) { - tcpOutput.close(); - tcpOutput = null; + if (localOutput != null) { + localOutput.close(); + localOutput = null; } setConnected(false); } catch (IOException ioException) { @@ -132,13 +136,13 @@ public void addIntesisBoxChangeListener(IntesisBoxChangeListener listener) { private void write(String data) { IntesisBoxChangeListener listener = this.changeListener; try { - if (tcpOutput != null) { - tcpOutput.write(data); - } - if (tcpOutput != null) { + OutputStreamWriter localOutput = tcpOutput; + + if (localOutput != null) { + localOutput.write(data); + localOutput.flush(); } } catch (IOException ioException) { - logger.debug("write(): {}", ioException.getMessage()); setConnected(false); if (listener != null) { listener.connectionStatusChanged(ThingStatus.OFFLINE, ioException.getMessage()); @@ -149,12 +153,11 @@ private void write(String data) { public String read() { String message = ""; try { - if (tcpInput != null) { - message = tcpInput.readLine(); - logger.debug("read(): Message Received: {}", message); + BufferedReader localInput = tcpInput; + if (localInput != null) { + message = localInput.readLine(); } } catch (IOException ioException) { - logger.debug("read(): IO Exception: {}", ioException.getMessage()); setConnected(false); } return message; diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java index 24dd736e9b21..083ced8c29bf 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java @@ -100,17 +100,12 @@ public void initialize() { intesisBoxSocketApi = new IntesisBoxSocketApi(config.ipAddress, config.port); intesisBoxSocketApi.addIntesisBoxChangeListener(this); try { - if (intesisBoxSocketApi != null) { - intesisBoxSocketApi.openConnection(); - } - if (intesisBoxSocketApi != null) { - intesisBoxSocketApi.sendId(); - } - if (intesisBoxSocketApi != null) { - intesisBoxSocketApi.sendLimitsQuery(); - } - if (intesisBoxSocketApi != null) { - intesisBoxSocketApi.sendAlive(); + IntesisBoxSocketApi intesisLocalApi = intesisBoxSocketApi; + if (intesisLocalApi != null) { + intesisLocalApi.openConnection(); + intesisLocalApi.sendId(); + intesisLocalApi.sendLimitsQuery(); + intesisLocalApi.sendAlive(); } } catch (IOException e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); @@ -279,7 +274,6 @@ private void receivedUpdate(String function, String receivedValue) { } } - @SuppressWarnings("null") private void handleMessage(String data) { logger.debug("handleMessage(): Message received - {}", data); if (data.equals("ACK") || data.equals("")) { @@ -290,7 +284,7 @@ private void handleMessage(String data) { return; } IntesisBoxMessage message = IntesisBoxMessage.parse(data); - if (message.getCommand() != null) { + if (message != null) { switch (message.getCommand()) { case LIMITS: logger.debug("handleMessage(): Limits received - {}", data); From 04cac768cfb05e18fd462a9cf5d28cec35bca6c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans-J=C3=B6rg=20Merk?= Date: Fri, 23 Oct 2020 09:05:45 +0200 Subject: [PATCH 6/8] Changes after review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hans-Jörg Merk --- .../internal/api/IntesisBoxSocketApi.java | 7 +++++++ .../internal/handler/IntesisBoxHandler.java | 21 ++++++++++--------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java index f1bf2ad18efc..7c9a51aa70b6 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java @@ -23,6 +23,7 @@ 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; @@ -200,4 +201,10 @@ public boolean isConnected() { public void setConnected(boolean connected) { this.connected = connected; } + + public void removeIntesisBoxChangeListener(IntesisBoxHandler intesisBoxHandler) { + if (this.changeListener != null) { + this.changeListener = null; + } + } } diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java index 083ced8c29bf..16af522ab7fe 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java @@ -97,22 +97,22 @@ public void initialize() { addChannel(CHANNEL_TYPE_ERRORCODE, "String"); addChannel(CHANNEL_TYPE_ERRORSTATUS, "String"); - intesisBoxSocketApi = new IntesisBoxSocketApi(config.ipAddress, config.port); - intesisBoxSocketApi.addIntesisBoxChangeListener(this); + IntesisBoxSocketApi intesisLocalApi = intesisBoxSocketApi = new IntesisBoxSocketApi(config.ipAddress, + config.port); + intesisLocalApi.addIntesisBoxChangeListener(this); try { - IntesisBoxSocketApi intesisLocalApi = intesisBoxSocketApi; - if (intesisLocalApi != null) { - intesisLocalApi.openConnection(); - intesisLocalApi.sendId(); - intesisLocalApi.sendLimitsQuery(); - intesisLocalApi.sendAlive(); - } + intesisLocalApi.openConnection(); + intesisLocalApi.sendId(); + intesisLocalApi.sendLimitsQuery(); + intesisLocalApi.sendAlive(); + } catch (IOException e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + return; } - pollingTask = scheduler.scheduleWithFixedDelay(this::polling, 3, 45, TimeUnit.SECONDS); updateStatus(ThingStatus.ONLINE); }); + pollingTask = scheduler.scheduleWithFixedDelay(this::polling, 3, 45, TimeUnit.SECONDS); } else { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No IP address specified)"); } @@ -130,6 +130,7 @@ public void dispose() { } if (api != null) { api.closeConnection(); + api.removeIntesisBoxChangeListener(this); } super.dispose(); } From f9b0cba605d648a2505e99361c7c7f7c97bf3a07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans-J=C3=B6rg=20Merk?= Date: Fri, 23 Oct 2020 17:05:53 +0200 Subject: [PATCH 7/8] Changed RSSI from property to channel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hans-Jörg Merk --- bundles/org.openhab.binding.intesis/README.md | 25 ++++++++------- .../internal/IntesisBindingConstants.java | 1 + .../internal/handler/IntesisBoxHandler.java | 32 ++++++++++++++++--- .../resources/OH-INF/thing/thing-types.xml | 1 + 4 files changed, 43 insertions(+), 16 deletions(-) diff --git a/bundles/org.openhab.binding.intesis/README.md b/bundles/org.openhab.binding.intesis/README.md index 6ecfbf743ce3..b3567c0f9fd7 100644 --- a/bundles/org.openhab.binding.intesis/README.md +++ b/bundles/org.openhab.binding.intesis/README.md @@ -30,18 +30,19 @@ The binding uses the following configuration parameters. ## 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 (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 | +| 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. diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisBindingConstants.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisBindingConstants.java index 783b1511369c..4503e96d728a 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisBindingConstants.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisBindingConstants.java @@ -44,4 +44,5 @@ public class IntesisBindingConstants { 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"; } diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java index 16af522ab7fe..7b8643faa1d9 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java @@ -34,6 +34,7 @@ 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; @@ -78,6 +79,8 @@ public class IntesisBoxHandler extends BaseThingHandler implements IntesisBoxCha private double minTemp = 0.0, maxTemp = 0.0; + private boolean hasProperties = false; + private @Nullable ScheduledFuture pollingTask; public IntesisBoxHandler(Thing thing, IntesisDynamicStateDescriptionProvider intesisStateDescriptionProvider) { @@ -146,6 +149,7 @@ private synchronized void polling() { } } api.sendAlive(); + api.sendId(); } } @@ -213,17 +217,16 @@ public void handleCommand(ChannelUID channelUID, Command command) { } } - private void populateProperties(String data) { - String[] value = data.substring(3).split(","); + 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("rssi", value[5]); properties.put("hostname", value[6]); updateProperties(properties); + hasProperties = true; } private void receivedUpdate(String function, String receivedValue) { @@ -281,7 +284,12 @@ private void handleMessage(String data) { return; } if (data.startsWith(ID + ':')) { - populateProperties(data); + 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); @@ -368,4 +376,20 @@ public void connectionStatusChanged(ThingStatus status, @Nullable String 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); + } } diff --git a/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/thing-types.xml index d57a816032ff..f78b357b08d4 100644 --- a/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/thing-types.xml @@ -29,6 +29,7 @@ Represents a single IntesisBox adapter on the network, connected to an A/C unit. + From bab56ce7327ab0d96f457dfb4ac4273752e67f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans-J=C3=B6rg=20Merk?= Date: Sat, 24 Oct 2020 09:56:29 +0200 Subject: [PATCH 8/8] Changes after next review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hans-Jörg Merk --- .../internal/IntesisHandlerFactory.java | 2 -- .../internal/api/IntesisBoxSocketApi.java | 12 ++++++---- .../internal/handler/IntesisBoxHandler.java | 9 ++++---- .../OH-INF/thing/dynamic-channels.xml | 10 --------- .../resources/OH-INF/thing/thing-types.xml | 22 +++++++++++++++++++ 5 files changed, 34 insertions(+), 21 deletions(-) diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisHandlerFactory.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisHandlerFactory.java index 72c4c171aece..fd48f81372c2 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisHandlerFactory.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/IntesisHandlerFactory.java @@ -73,12 +73,10 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { 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)) { - logger.debug("Creating a IntesisBoxHandler for thing '{}'", thing.getUID()); return new IntesisBoxHandler(thing, intesisStateDescriptionProvider); } diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java index 7c9a51aa70b6..c087532abce6 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/api/IntesisBoxSocketApi.java @@ -20,6 +20,7 @@ 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; @@ -41,6 +42,7 @@ public class IntesisBoxSocketApi { private final String ipAddress; private final int port; + private final String readerThreadName; private @Nullable IntesisSocket tcpSocket = null; private @Nullable OutputStreamWriter tcpOutput = null; @@ -50,9 +52,10 @@ public class IntesisBoxSocketApi { private boolean connected = false; - public IntesisBoxSocketApi(final String ipAddress, final int port) { + public IntesisBoxSocketApi(final String ipAddress, final int port, final String readerThreadName) { this.ipAddress = ipAddress; this.port = port; + this.readerThreadName = readerThreadName; } private class IntesisSocket { @@ -74,11 +77,12 @@ public void openConnection() throws IOException { IntesisBoxChangeListener listener = this.changeListener; IntesisSocket localSocket = tcpSocket = new IntesisSocket(); - tcpOutput = new OutputStreamWriter(localSocket.socket.getOutputStream(), "US-ASCII"); - tcpInput = new BufferedReader(new InputStreamReader(localSocket.socket.getInputStream(), "US-ASCII")); + 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("IntesisBoxTCPListener"); + tcpListener.setName(readerThreadName); tcpListener.setDaemon(true); tcpListener.start(); diff --git a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java index 7b8643faa1d9..ee56499b0771 100644 --- a/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java +++ b/bundles/org.openhab.binding.intesis/src/main/java/org/openhab/binding/intesis/internal/handler/IntesisBoxHandler.java @@ -71,7 +71,7 @@ public class IntesisBoxHandler extends BaseThingHandler implements IntesisBoxCha private @Nullable IntesisBoxSocketApi intesisBoxSocketApi; private final Map properties = new HashMap<>(); - private final Map> limits = new HashMap>(); + private final Map> limits = new HashMap<>(); private final IntesisDynamicStateDescriptionProvider intesisStateDescriptionProvider; @@ -96,12 +96,11 @@ public void initialize() { updateStatus(ThingStatus.UNKNOWN); scheduler.submit(() -> { - addChannel(CHANNEL_TYPE_AMBIENTTEMP, "Number:Temperature"); - addChannel(CHANNEL_TYPE_ERRORCODE, "String"); - addChannel(CHANNEL_TYPE_ERRORSTATUS, "String"); + + String readerThreadName = "OH-binding-" + getThing().getUID().getAsString(); IntesisBoxSocketApi intesisLocalApi = intesisBoxSocketApi = new IntesisBoxSocketApi(config.ipAddress, - config.port); + config.port, readerThreadName); intesisLocalApi.addIntesisBoxChangeListener(this); try { intesisLocalApi.openConnection(); diff --git a/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/dynamic-channels.xml b/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/dynamic-channels.xml index 0615c7c75db0..89e9a832f6f2 100644 --- a/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/dynamic-channels.xml +++ b/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/dynamic-channels.xml @@ -42,15 +42,5 @@ @text/channel-type.intesis.outdoorTemperature.description - - String - - @text/channel-type.intesis.errorCode.description - - - String - - @text/channel-type.intesis.errorStatus.description - diff --git a/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/thing-types.xml index f78b357b08d4..f8bd16002aa5 100644 --- a/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.intesis/src/main/resources/OH-INF/thing/thing-types.xml @@ -30,6 +30,9 @@ + + + @@ -45,4 +48,23 @@ + + Number:Temperature + + @text/channel-type.intesis.ambientTemperature.description + + + + + String + + @text/channel-type.intesis.errorCode.description + + + + String + + @text/channel-type.intesis.errorStatus.description + +