]> git.basschouten.com Git - openhab-addons.git/blob
3699b99fbb0e00f728601dc61a1bc04a86f82ad9
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.dominoswiss.internal;
14
15 import static org.openhab.binding.dominoswiss.internal.DominoswissBindingConstants.*;
16
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;
26 import java.util.Map;
27 import java.util.concurrent.Future;
28 import java.util.concurrent.ScheduledFuture;
29 import java.util.concurrent.TimeUnit;
30
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;
43
44 /**
45  * The {@link EGateHandler} is responsible for handling commands, which are
46  * sent to one of the channels.
47  *
48  * @author Frieso Aeschbacher - Initial contribution
49  */
50 @NonNullByDefault
51 public class EGateHandler extends BaseBridgeHandler {
52
53     private final Logger logger = LoggerFactory.getLogger(EGateHandler.class);
54     private @Nullable Socket egateSocket;
55
56     private int port;
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;
65
66     public EGateHandler(Bridge thing) {
67         super(thing);
68         registeredBlinds = new HashMap<String, ThingUID>();
69     }
70
71     @Override
72     public void handleCommand(ChannelUID channelUID, Command command) {
73         if (channelUID.getId().equals(GETCONFIG)) {
74             sendCommand("EthernetGet;\r");
75         }
76     }
77
78     @Override
79     public void initialize() {
80         DominoswissConfiguration config;
81         config = this.getConfigAs(DominoswissConfiguration.class);
82         host = config.ipAddress;
83         port = config.port;
84
85         if (host != null && port > 0) {
86             // Create a socket to eGate
87
88             InetSocketAddress socketAddress = new InetSocketAddress(host, port);
89             Socket localEgateSocket = new Socket();
90             try {
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());
99                 egateSocket = null;
100             }
101             pollingJob = scheduler.scheduleWithFixedDelay(this::pollingConfig, 0, 30, TimeUnit.SECONDS);
102         } else {
103             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
104                     "Cannot connect to dominoswiss eGate gateway. host IP address or port are not set.");
105         }
106     }
107
108     @Override
109     public void dispose() {
110         try {
111             Socket localEgateSocket = egateSocket;
112             if (localEgateSocket != null) {
113                 localEgateSocket.close();
114             }
115             Future<?> localRefreshJob = refreshJob;
116             if (localRefreshJob != null) {
117                 localRefreshJob.cancel(true);
118             }
119             BufferedReader localReader = reader;
120             if (localReader != null) {
121                 localReader.close();
122             }
123
124             BufferedWriter localWriter = writer;
125             if (localWriter != null) {
126                 localWriter.close();
127             }
128             ScheduledFuture<?> localPollingJob = pollingJob;
129             if (localPollingJob != null) {
130                 localPollingJob.cancel(true);
131                 localPollingJob = null;
132             }
133             logger.debug("EGate Handler connection closed, disposing");
134         } catch (IOException e) {
135             logger.debug("EGate Handler Error on dispose: {} ", e.toString());
136         }
137     }
138
139     public synchronized boolean isConnected() {
140         Socket localEGateSocket = egateSocket;
141         if (localEGateSocket == null) {
142             logger.debug("EGate is not connected, Socket is null");
143             return false;
144         }
145
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());
151
152         return localEGateSocket.isConnected() && !localEGateSocket.isClosed();
153     }
154
155     /**
156      * Possible Instructions are:
157      * FssTransmit 1 Kommandoabsetzung (Controller > eGate > Dominoswiss)
158      * FssReceive 2 Empfangenes Funkpaket (Dominoswiss > eGate > Controller)
159      *
160      * @throws InterruptedException
161      *
162      */
163
164     public void tiltUp(String id) throws InterruptedException {
165         for (int i = 0; i < 3; i++) {
166             pulseUp(id);
167             Thread.sleep(150); // sleep to not confuse the blinds
168
169         }
170     }
171
172     public void tiltDown(String id) throws InterruptedException {
173         for (int i = 0; i < 3; i++) {
174             pulseDown(id);
175             Thread.sleep(150);// sleep to not confuse the blinds
176         }
177     }
178
179     public void pulseUp(String id) {
180         sendCommand("Instruction=1;ID=" + id + ";Command=1;Priority=1;CheckNr=3415347;" + CR);
181     }
182
183     public void pulseDown(String id) {
184         sendCommand("Instruction=1;ID=" + id + ";Command=2;Priority=1;CheckNr=2764516;" + CR);
185     }
186
187     public void continuousUp(String id) {
188         sendCommand("Instruction=1;ID=" + id + ";Command=3;Priority=1;CheckNr=2867016;" + CR, 20000);
189     }
190
191     public void continuousDown(String id) {
192         sendCommand("Instruction=1;ID=" + id + ";Command=4;Priority=1;CheckNr=973898;" + CR, 20000);
193     }
194
195     public void stop(String id) {
196         sendCommand("Instruction=1;ID=" + id + ";Command=5;Priority=1;CheckNr=5408219;" + CR);
197     }
198
199     public void registerBlind(String id, ThingUID uid) {
200         logger.debug("Registring Blind id {} with thingUID {}", id, uid);
201         registeredBlinds.put(id, uid);
202     }
203
204     /**
205      * Send a command to the eGate Server.
206      */
207
208     private void sendCommand(String command) {
209         sendCommand(command, SOCKET_TIMEOUT_MILLISEC);
210     }
211
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...");
218             return;
219         }
220         if (!isConnected()) {
221             logger.debug("no connection to Dominoswiss eGate server when trying to send command, returning...");
222             return;
223         }
224
225         // Send plain string to eGate Server,
226         try {
227             localEGateSocket.setSoTimeout(timeout);
228             localWriter.write(command);
229             localWriter.flush();
230         } catch (IOException e) {
231             logger.debug("Error while sending command {} to Dominoswiss eGate Server {} ", command, e.toString());
232         }
233     }
234
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...");
242                 return;
243             }
244
245             synchronized (lock) {
246                 try {
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);
250                     localWriter.flush();
251                 } catch (IOException e) {
252                     logger.debug("IOException in pollingConfig: {} host {} port {}", e.toString(), host, port);
253                     try {
254                         localEGateSocket.close();
255                         egateSocket = null;
256                         logger.debug("EGate closed");
257                     } catch (IOException e1) {
258                         logger.debug("EGate Socket not closed {}", e1.toString());
259                     }
260                     egateSocket = null;
261                 }
262                 if (egateSocket != null) {
263                     updateStatus(ThingStatus.ONLINE);
264                     startAutomaticRefresh();
265                     logger.debug("EGate Handler started automatic refresh, status: {} ",
266                             getThing().getStatus().toString());
267                 } else {
268                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
269                 }
270             }
271         }
272     }
273
274     private void startAutomaticRefresh() {
275         Runnable runnable = () -> {
276             try {
277                 Socket localSocket = egateSocket;
278                 if (localSocket == null) {
279                     return;
280                 }
281                 BufferedReader localReader = reader;
282                 if (localReader == null) {
283                     reader = new BufferedReader(new InputStreamReader(localSocket.getInputStream()));
284                 }
285                 if (localReader != null && localReader.ready()) {
286                     String input = localReader.readLine();
287                     logger.debug("Reader got from EGATE: {}", input);
288                     onData(input);
289                 }
290             } catch (IOException e) {
291                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
292                         "Error while reading command from Dominoswiss eGate Server " + e.toString());
293             }
294         };
295         refreshJob = scheduler.submit(runnable);
296     }
297
298     /**
299      * Finds and returns a child thing for a given UID of this bridge.
300      *
301      * @param uid uid of the child thing
302      * @return child thing with the given uid or null if thing was not found
303      */
304     public @Nullable Thing getThingByUID(ThingUID uid) {
305         Bridge bridge = getThing();
306
307         List<Thing> things = bridge.getThings();
308
309         for (Thing thing : things) {
310             if (thing.getUID().equals(uid)) {
311                 return thing;
312             }
313         }
314
315         return null;
316     }
317
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>();
321         // split on ;
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]);
326             }
327         }
328     }
329 }