2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.dominoswiss.internal;
15 import static org.openhab.binding.dominoswiss.internal.DominoswissBindingConstants.*;
17 import java.io.BufferedReader;
18 import java.io.BufferedWriter;
19 import java.io.IOException;
20 import java.io.InputStreamReader;
21 import java.io.OutputStreamWriter;
22 import java.net.InetSocketAddress;
23 import java.net.Socket;
24 import java.util.HashMap;
25 import java.util.List;
27 import java.util.concurrent.Future;
28 import java.util.concurrent.ScheduledFuture;
29 import java.util.concurrent.TimeUnit;
31 import org.eclipse.jdt.annotation.NonNullByDefault;
32 import org.eclipse.jdt.annotation.Nullable;
33 import org.openhab.core.thing.Bridge;
34 import org.openhab.core.thing.ChannelUID;
35 import org.openhab.core.thing.Thing;
36 import org.openhab.core.thing.ThingStatus;
37 import org.openhab.core.thing.ThingStatusDetail;
38 import org.openhab.core.thing.ThingUID;
39 import org.openhab.core.thing.binding.BaseBridgeHandler;
40 import org.openhab.core.types.Command;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
45 * The {@link EGateHandler} is responsible for handling commands, which are
46 * sent to one of the channels.
48 * @author Frieso Aeschbacher - Initial contribution
51 public class EGateHandler extends BaseBridgeHandler {
53 private final Logger logger = LoggerFactory.getLogger(EGateHandler.class);
54 private @Nullable Socket egateSocket;
57 private @Nullable String host;
58 private static final int SOCKET_TIMEOUT_MILLISEC = 1000;
59 private final Object lock = new Object();
60 private @Nullable BufferedWriter writer;
61 private @Nullable BufferedReader reader;
62 private @Nullable Future<?> refreshJob;
63 private Map<String, ThingUID> registeredBlinds;
64 private @Nullable ScheduledFuture<?> pollingJob;
66 public EGateHandler(Bridge thing) {
68 registeredBlinds = new HashMap<String, ThingUID>();
72 public void handleCommand(ChannelUID channelUID, Command command) {
73 if (channelUID.getId().equals(GETCONFIG)) {
74 sendCommand("EthernetGet;\r");
79 public void initialize() {
80 DominoswissConfiguration config;
81 config = this.getConfigAs(DominoswissConfiguration.class);
82 host = config.ipAddress;
85 if (host != null && port > 0) {
86 // Create a socket to eGate
88 InetSocketAddress socketAddress = new InetSocketAddress(host, port);
89 Socket localEgateSocket = new Socket();
91 localEgateSocket.connect(socketAddress, SOCKET_TIMEOUT_MILLISEC);
92 writer = new BufferedWriter(new OutputStreamWriter(localEgateSocket.getOutputStream()));
93 egateSocket = localEgateSocket;
94 updateStatus(ThingStatus.ONLINE);
95 logger.debug("Egate successfully connected {}", egateSocket.toString());
96 } catch (IOException e) {
97 logger.debug("IOException in initialize: {} host {} port {}", e.toString(), host, port);
98 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.toString());
101 pollingJob = scheduler.scheduleWithFixedDelay(this::pollingConfig, 0, 30, TimeUnit.SECONDS);
103 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
104 "Cannot connect to dominoswiss eGate gateway. host IP address or port are not set.");
109 public void dispose() {
111 Socket localEgateSocket = egateSocket;
112 if (localEgateSocket != null) {
113 localEgateSocket.close();
115 Future<?> localRefreshJob = refreshJob;
116 if (localRefreshJob != null) {
117 localRefreshJob.cancel(true);
119 BufferedReader localReader = reader;
120 if (localReader != null) {
124 BufferedWriter localWriter = writer;
125 if (localWriter != null) {
128 ScheduledFuture<?> localPollingJob = pollingJob;
129 if (localPollingJob != null) {
130 localPollingJob.cancel(true);
131 localPollingJob = null;
133 logger.debug("EGate Handler connection closed, disposing");
134 } catch (IOException e) {
135 logger.debug("EGate Handler Error on dispose: {} ", e.toString());
139 public synchronized boolean isConnected() {
140 Socket localEGateSocket = egateSocket;
141 if (localEGateSocket == null) {
142 logger.debug("EGate is not connected, Socket is null");
146 // NOTE: isConnected() returns true once a connection is made and will
147 // always return true even after the socket is closed
148 // http://stackoverflow.com/questions/10163358/
149 logger.debug("EGate isconnected() {}, isClosed() {}", localEGateSocket.isConnected(),
150 localEGateSocket.isClosed());
152 return localEGateSocket.isConnected() && !localEGateSocket.isClosed();
156 * Possible Instructions are:
157 * FssTransmit 1 Kommandoabsetzung (Controller > eGate > Dominoswiss)
158 * FssReceive 2 Empfangenes Funkpaket (Dominoswiss > eGate > Controller)
160 * @throws InterruptedException
164 public void tiltUp(String id) throws InterruptedException {
165 for (int i = 0; i < 3; i++) {
167 Thread.sleep(150); // sleep to not confuse the blinds
172 public void tiltDown(String id) throws InterruptedException {
173 for (int i = 0; i < 3; i++) {
175 Thread.sleep(150);// sleep to not confuse the blinds
179 public void pulseUp(String id) {
180 sendCommand("Instruction=1;ID=" + id + ";Command=1;Priority=1;CheckNr=3415347;" + CR);
183 public void pulseDown(String id) {
184 sendCommand("Instruction=1;ID=" + id + ";Command=2;Priority=1;CheckNr=2764516;" + CR);
187 public void continuousUp(String id) {
188 sendCommand("Instruction=1;ID=" + id + ";Command=3;Priority=1;CheckNr=2867016;" + CR, 20000);
191 public void continuousDown(String id) {
192 sendCommand("Instruction=1;ID=" + id + ";Command=4;Priority=1;CheckNr=973898;" + CR, 20000);
195 public void stop(String id) {
196 sendCommand("Instruction=1;ID=" + id + ";Command=5;Priority=1;CheckNr=5408219;" + CR);
199 public void registerBlind(String id, ThingUID uid) {
200 logger.debug("Registring Blind id {} with thingUID {}", id, uid);
201 registeredBlinds.put(id, uid);
205 * Send a command to the eGate Server.
208 private void sendCommand(String command) {
209 sendCommand(command, SOCKET_TIMEOUT_MILLISEC);
212 private synchronized void sendCommand(String command, int timeout) {
213 logger.debug("EGate got command: {}", command);
214 Socket localEGateSocket = egateSocket;
215 BufferedWriter localWriter = writer;
216 if (localEGateSocket == null || localWriter == null) {
217 logger.debug("Error eGateSocket null, writer null, returning...");
220 if (!isConnected()) {
221 logger.debug("no connection to Dominoswiss eGate server when trying to send command, returning...");
225 // Send plain string to eGate Server,
227 localEGateSocket.setSoTimeout(timeout);
228 localWriter.write(command);
230 } catch (IOException e) {
231 logger.debug("Error while sending command {} to Dominoswiss eGate Server {} ", command, e.toString());
235 private void pollingConfig() {
236 if (!isConnected()) {
237 logger.debug("PollingConfig Run, is not connected so let's connect");
238 Socket localEGateSocket = egateSocket;
239 BufferedWriter localWriter = writer;
240 if (localEGateSocket == null || localWriter == null) {
241 logger.debug("Error eGateSocket null, writer null in pollingConfig(), returning...");
245 synchronized (lock) {
247 localEGateSocket.connect(new InetSocketAddress(host, port), SOCKET_TIMEOUT_MILLISEC);
248 logger.debug("pollingConfig() successsully connected {}", localEGateSocket.isClosed());
249 localWriter.write("SilenceModeSet;Value=0;" + CR);
251 } catch (IOException e) {
252 logger.debug("IOException in pollingConfig: {} host {} port {}", e.toString(), host, port);
254 localEGateSocket.close();
256 logger.debug("EGate closed");
257 } catch (IOException e1) {
258 logger.debug("EGate Socket not closed {}", e1.toString());
262 if (egateSocket != null) {
263 updateStatus(ThingStatus.ONLINE);
264 startAutomaticRefresh();
265 logger.debug("EGate Handler started automatic refresh, status: {} ",
266 getThing().getStatus().toString());
268 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
274 private void startAutomaticRefresh() {
275 Runnable runnable = () -> {
277 Socket localSocket = egateSocket;
278 if (localSocket == null) {
281 BufferedReader localReader = reader;
282 if (localReader == null) {
283 reader = new BufferedReader(new InputStreamReader(localSocket.getInputStream()));
285 if (localReader != null && localReader.ready()) {
286 String input = localReader.readLine();
287 logger.debug("Reader got from EGATE: {}", input);
290 } catch (IOException e) {
291 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
292 "Error while reading command from Dominoswiss eGate Server " + e.toString());
295 refreshJob = scheduler.submit(runnable);
299 * Finds and returns a child thing for a given UID of this bridge.
301 * @param uid uid of the child thing
302 * @return child thing with the given uid or null if thing was not found
304 public @Nullable Thing getThingByUID(ThingUID uid) {
305 Bridge bridge = getThing();
307 List<Thing> things = bridge.getThings();
309 for (Thing thing : things) {
310 if (thing.getUID().equals(uid)) {
318 protected void onData(String input) {
319 // Instruction=2;ID=19;Command=1;Value=0;Priority=0;
320 Map<String, String> map = new HashMap<String, String>();
322 String[] parts = input.split(";");
323 if (parts.length >= 2) {
324 for (int i = 0; i < parts.length; i += 2) {
325 map.put(parts[i], parts[i + 1]);