]> git.basschouten.com Git - openhab-addons.git/blob
e5132e894b9d28a1d8d751597d3164d495536c6f
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.vitotronic.internal.handler;
14
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.io.PrintStream;
18 import java.net.Socket;
19 import java.net.UnknownHostException;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.Map;
23 import java.util.Set;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
26
27 import org.openhab.binding.vitotronic.internal.VitotronicBindingConfiguration;
28 import org.openhab.binding.vitotronic.internal.discovery.VitotronicDiscoveryService;
29 import org.openhab.core.thing.Bridge;
30 import org.openhab.core.thing.ChannelUID;
31 import org.openhab.core.thing.ThingStatus;
32 import org.openhab.core.thing.binding.BaseBridgeHandler;
33 import org.openhab.core.types.Command;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36 import org.xml.sax.Attributes;
37 import org.xml.sax.ContentHandler;
38 import org.xml.sax.InputSource;
39 import org.xml.sax.Locator;
40 import org.xml.sax.SAXException;
41 import org.xml.sax.XMLReader;
42 import org.xml.sax.helpers.XMLReaderFactory;
43
44 /**
45  * The {@link VitotronicBridgeHandler} class handles the connection to the
46  * optolink adapter.
47  *
48  * @author Stefan Andres - Initial contribution
49  */
50 public class VitotronicBridgeHandler extends BaseBridgeHandler {
51
52     private final Logger logger = LoggerFactory.getLogger(VitotronicBridgeHandler.class);
53
54     private String ipAddress;
55     private int port;
56     private int refreshInterval = 300;
57     private Socket socket;
58     private PrintStream out;
59     private InputStream inStream;
60     private boolean isConnect = false;
61     private boolean isDiscover = false;
62
63     public VitotronicBridgeHandler(Bridge bridge) {
64         super(bridge);
65     }
66
67     @Override
68     public void updateStatus(ThingStatus status) {
69         super.updateStatus(status);
70         updateThingHandlersStatus(status);
71     }
72
73     public void updateStatus() {
74         if (isConnect) {
75             updateStatus(ThingStatus.ONLINE);
76         } else {
77             updateStatus(ThingStatus.OFFLINE);
78         }
79     }
80
81     // Managing Thing Discovery Service
82
83     private VitotronicDiscoveryService discoveryService = null;
84
85     public void registerDiscoveryService(VitotronicDiscoveryService discoveryService) {
86         if (discoveryService == null) {
87             throw new IllegalArgumentException("It's not allowed to pass a null ThingDiscoveryListener.");
88         } else {
89             this.discoveryService = discoveryService;
90             logger.trace("register Discovery Service");
91         }
92     }
93
94     public void unregisterDiscoveryService() {
95         discoveryService = null;
96         logger.trace("unregister Discovery Service");
97     }
98
99     // Handles Thing discovery
100
101     private void createThing(String thingType, String thingID) {
102         logger.trace("Create thing Type='{}' id='{}'", thingType, thingID);
103         if (discoveryService != null) {
104             discoveryService.addVitotronicThing(thingType, thingID);
105         }
106     }
107
108     // Managing ThingHandler
109
110     private Map<String, VitotronicThingHandler> thingHandlerMap = new HashMap<>();
111
112     public void registerVitotronicThingListener(VitotronicThingHandler thingHandler) {
113         if (thingHandler == null) {
114             throw new IllegalArgumentException("It's not allowed to pass a null ThingHandler.");
115         } else {
116             String thingID = thingHandler.getThing().getUID().getId();
117             if (thingHandlerMap.get(thingID) == null) {
118                 thingHandlerMap.put(thingID, thingHandler);
119                 logger.trace("register thingHandler for thing: {}", thingID);
120                 updateThingHandlerStatus(thingHandler, this.getStatus());
121                 sendSocketData("get " + thingID);
122             } else {
123                 logger.trace("thingHandler for thing: '{}' already registered", thingID);
124             }
125         }
126     }
127
128     public void unregisterThingListener(VitotronicThingHandler thingHandler) {
129         if (thingHandler != null) {
130             String thingID = thingHandler.getThing().getUID().getId();
131             if (thingHandlerMap.remove(thingID) == null) {
132                 logger.trace("thingHandler for thing: {} not registered", thingID);
133             }
134         }
135     }
136
137     private void updateThingHandlerStatus(VitotronicThingHandler thingHandler, ThingStatus status) {
138         thingHandler.updateStatus(status);
139     }
140
141     private void updateThingHandlersStatus(ThingStatus status) {
142         for (Map.Entry<String, VitotronicThingHandler> entry : thingHandlerMap.entrySet()) {
143             updateThingHandlerStatus(entry.getValue(), status);
144         }
145     }
146
147     // Background Runables
148
149     private ScheduledFuture<?> pollingJob;
150
151     private Runnable pollingRunnable = () -> {
152         logger.trace("Polling job called");
153         if (!isConnect) {
154             startSocketReceiver();
155             try {
156                 Thread.sleep(5000); // Wait for connection .
157             } catch (InterruptedException e) {
158             }
159         }
160         if (isConnect) {
161             scanThings();
162             refreshData();
163         }
164     };
165
166     private synchronized void startAutomaticRefresh() {
167         if (pollingJob == null || pollingJob.isCancelled()) {
168             pollingJob = scheduler.scheduleWithFixedDelay(pollingRunnable, 0, refreshInterval, TimeUnit.SECONDS);
169         }
170     }
171
172     private void refreshData() {
173         logger.trace("Job: refresh Data...");
174         for (Map.Entry<String, VitotronicThingHandler> entry : thingHandlerMap.entrySet()) {
175             String channelList = entry.getValue().getActiveChannelListAsString();
176             String thingId = entry.getValue().getThing().getUID().getId();
177             if (isConnect && (channelList.length() > 0)) {
178                 logger.trace("Get Data for '{}'", thingId);
179                 sendSocketData("get " + thingId + " " + channelList);
180             }
181         }
182     }
183
184     // Methods for ThingHandler
185
186     public void scanThings() {
187         logger.trace("Job: Discover Things...");
188         if (!isDiscover) {
189             sendSocketData("list");
190             isDiscover = true;
191         }
192     }
193
194     public ThingStatus getStatus() {
195         return getThing().getStatus();
196     }
197
198     public void updateChannel(String thingId, String channelId, String value) {
199         sendSocketData("set " + thingId + ":" + channelId + " " + value);
200     }
201
202     // internal Methods
203
204     @Override
205     public void handleCommand(ChannelUID channelUID, Command command) {
206         // No channels - nothing to do
207     }
208
209     @Override
210     public void initialize() {
211         logger.debug("Initializing Vitotronic bridge handler {}", getThing().getUID());
212         updateStatus();
213         VitotronicBindingConfiguration configuration = getConfigAs(VitotronicBindingConfiguration.class);
214         ipAddress = configuration.ipAddress;
215         port = configuration.port;
216         refreshInterval = configuration.refreshInterval;
217
218         isDiscover = false;
219         startAutomaticRefresh();
220     }
221
222     @Override
223     public void dispose() {
224         logger.debug("Dispose Vitotronic bridge handler {}", getThing().getUID());
225
226         if (pollingJob != null && !pollingJob.isCancelled()) {
227             pollingJob.cancel(true);
228             pollingJob = null;
229         }
230     }
231
232     // Connection to adapter
233
234     private void openSocket() {
235         logger.trace("Try to open connection to Optolink Adapter {}:{}", ipAddress, port);
236
237         try {
238             socket = new Socket(ipAddress, port);
239             out = new PrintStream(socket.getOutputStream());
240             inStream = socket.getInputStream();
241         } catch (UnknownHostException e) {
242             logger.error("Can't find Host: {}:{}", ipAddress, port);
243         } catch (IOException e) {
244             logger.debug("Error in communication to Host: {}:{}", ipAddress, port);
245             logger.trace("Diagnostic: ", e);
246         }
247     }
248
249     Runnable socketReceiverRunnable = () -> {
250         logger.trace("Start Background Thread for recieving data from adapter");
251         try {
252             XMLReader xmlReader = XMLReaderFactory.createXMLReader();
253             xmlReader.setContentHandler(new XmlHandler());
254             logger.trace("Start Parser for optolink adapter");
255             xmlReader.parse(new InputSource(inStream));
256
257         } catch (IOException e) {
258             logger.trace("Connection error from optolink adapter");
259         } catch (SAXException e) {
260             logger.trace("XML Parser Error");
261
262         }
263         updateStatus(ThingStatus.OFFLINE);
264         isConnect = false;
265         try {
266             if (!socket.isClosed()) {
267                 socket.close();
268             }
269         } catch (Exception e) {
270         }
271         logger.trace("Connection to optolink adapter is died ... wait for restart");
272     };
273
274     private void startSocketReceiver() {
275         if (!isConnect) {
276             openSocket();
277
278             Thread thread = new Thread(socketReceiverRunnable);
279             thread.setName("VitotronicSocketThread");
280             thread.start();
281         }
282     }
283
284     private void sendSocketData(String message) {
285         try {
286             logger.trace("Send Message {}", message);
287             if (isConnect) {
288                 if (message.matches("^set.*REFRESH$")) {
289                     String[] msgParts = message.split(" ");
290                     String[] thingChannel = msgParts[1].split(":");
291                     message = "get " + thingChannel[0] + " " + thingChannel[1];
292                 }
293                 out.write((message + "\n").getBytes());
294             }
295         } catch (IOException e) {
296             logger.error("Error in sending data to optolink adapter");
297             logger.trace("Diagnostic: ", e);
298         }
299     }
300
301     // Handles all data what received from optolink adapter
302
303     public class XmlHandler implements ContentHandler {
304         boolean isData;
305         boolean isDefine;
306         boolean isThing;
307         boolean isChannel;
308         boolean isDescription;
309         String thingID;
310         String thingType;
311         String channelID;
312         String description;
313         VitotronicThingHandler thingHandler;
314         Set<String> channels = new HashSet<>();
315
316         @Override
317         public void startElement(String uri, String localName, String pName, Attributes attr) throws SAXException {
318             try {
319                 switch (localName) {
320                     case "optolink":
321                         isConnect = true;
322                         updateStatus(ThingStatus.ONLINE);
323                         break;
324                     case "data":
325                         isDefine = false;
326                         break;
327                     case "define":
328                         isDefine = true;
329                         break;
330                     case "description":
331                         isDescription = true;
332                         break;
333                     case "thing":
334                         isThing = true;
335                         if (isDefine) {
336                             thingType = attr.getValue("type");
337                         }
338                         thingID = attr.getValue("id");
339                         channels.clear();
340                         thingHandler = thingHandlerMap.get(thingID);
341                         break;
342                     case "channel":
343                         isChannel = true;
344                         channelID = attr.getValue("id");
345                         if (isDefine) {
346                             channels.add(channelID);
347                         } else { // is data
348                             if (thingHandler != null) {
349                                 logger.trace("Set Data for channel '{}' value '{}'", channelID, attr.getValue("value"));
350                                 thingHandler.setChannelValue(channelID, attr.getValue("value"));
351                             }
352                         }
353                         break;
354                 }
355             } catch (Exception e) {
356                 logger.error("Error in parsing data");
357                 logger.trace("Diagnostic: ", e);
358             }
359         }
360
361         @Override
362         public void characters(char[] ch, int start, int length) throws SAXException {
363             if (isDescription) {
364                 description = new String(ch, start, length);
365             }
366         }
367
368         @Override
369         public void endElement(String uri, String localName, String qName) throws SAXException {
370             switch (localName) {
371                 case "description":
372                     isDescription = false;
373                     break;
374                 case "thing":
375                     if (isDefine) {
376                         createThing(thingType, thingID);
377                     }
378                     isThing = false;
379                     thingHandler = null;
380                     break;
381                 case "channel":
382                     isChannel = false;
383                     break;
384             }
385         }
386
387         // Unused function of xmlReader
388         @Override
389         public void endDocument() throws SAXException {
390         }
391
392         @Override
393         public void ignorableWhitespace(char[] arg0, int arg1, int arg2) throws SAXException {
394         }
395
396         @Override
397         public void processingInstruction(String arg0, String arg1) throws SAXException {
398         }
399
400         @Override
401         public void setDocumentLocator(Locator arg0) {
402         }
403
404         @Override
405         public void skippedEntity(String arg0) throws SAXException {
406         }
407
408         @Override
409         public void startDocument() throws SAXException {
410         }
411
412         @Override
413         public void startPrefixMapping(String arg0, String arg1) throws SAXException {
414         }
415
416         @Override
417         public void endPrefixMapping(String prefix) throws SAXException {
418         }
419     }
420 }