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.vitotronic.internal.handler;
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;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
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;
45 * The {@link VitotronicBridgeHandler} class handles the connection to the
48 * @author Stefan Andres - Initial contribution
50 public class VitotronicBridgeHandler extends BaseBridgeHandler {
52 private final Logger logger = LoggerFactory.getLogger(VitotronicBridgeHandler.class);
54 private String ipAddress;
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;
63 public VitotronicBridgeHandler(Bridge bridge) {
68 public void updateStatus(ThingStatus status) {
69 super.updateStatus(status);
70 updateThingHandlersStatus(status);
73 public void updateStatus() {
75 updateStatus(ThingStatus.ONLINE);
77 updateStatus(ThingStatus.OFFLINE);
81 // Managing Thing Discovery Service
83 private VitotronicDiscoveryService discoveryService = null;
85 public void registerDiscoveryService(VitotronicDiscoveryService discoveryService) {
86 if (discoveryService == null) {
87 throw new IllegalArgumentException("It's not allowed to pass a null ThingDiscoveryListener.");
89 this.discoveryService = discoveryService;
90 logger.trace("register Discovery Service");
94 public void unregisterDiscoveryService() {
95 discoveryService = null;
96 logger.trace("unregister Discovery Service");
99 // Handles Thing discovery
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);
108 // Managing ThingHandler
110 private Map<String, VitotronicThingHandler> thingHandlerMap = new HashMap<>();
112 public void registerVitotronicThingListener(VitotronicThingHandler thingHandler) {
113 if (thingHandler == null) {
114 throw new IllegalArgumentException("It's not allowed to pass a null ThingHandler.");
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);
123 logger.trace("thingHandler for thing: '{}' already registered", thingID);
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);
137 private void updateThingHandlerStatus(VitotronicThingHandler thingHandler, ThingStatus status) {
138 thingHandler.updateStatus(status);
141 private void updateThingHandlersStatus(ThingStatus status) {
142 for (Map.Entry<String, VitotronicThingHandler> entry : thingHandlerMap.entrySet()) {
143 updateThingHandlerStatus(entry.getValue(), status);
147 // Background Runables
149 private ScheduledFuture<?> pollingJob;
151 private Runnable pollingRunnable = () -> {
152 logger.trace("Polling job called");
154 startSocketReceiver();
156 Thread.sleep(5000); // Wait for connection .
157 } catch (InterruptedException e) {
166 private synchronized void startAutomaticRefresh() {
167 if (pollingJob == null || pollingJob.isCancelled()) {
168 pollingJob = scheduler.scheduleWithFixedDelay(pollingRunnable, 0, refreshInterval, TimeUnit.SECONDS);
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);
184 // Methods for ThingHandler
186 public void scanThings() {
187 logger.trace("Job: Discover Things...");
189 sendSocketData("list");
194 public ThingStatus getStatus() {
195 return getThing().getStatus();
198 public void updateChannel(String thingId, String channelId, String value) {
199 sendSocketData("set " + thingId + ":" + channelId + " " + value);
205 public void handleCommand(ChannelUID channelUID, Command command) {
206 // No channels - nothing to do
210 public void initialize() {
211 logger.debug("Initializing Vitotronic bridge handler {}", getThing().getUID());
213 VitotronicBindingConfiguration configuration = getConfigAs(VitotronicBindingConfiguration.class);
214 ipAddress = configuration.ipAddress;
215 port = configuration.port;
216 refreshInterval = configuration.refreshInterval;
219 startAutomaticRefresh();
223 public void dispose() {
224 logger.debug("Dispose Vitotronic bridge handler {}", getThing().getUID());
226 if (pollingJob != null && !pollingJob.isCancelled()) {
227 pollingJob.cancel(true);
232 // Connection to adapter
234 private void openSocket() {
235 logger.trace("Try to open connection to Optolink Adapter {}:{}", ipAddress, port);
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);
249 Runnable socketReceiverRunnable = () -> {
250 logger.trace("Start Background Thread for recieving data from adapter");
252 XMLReader xmlReader = XMLReaderFactory.createXMLReader();
253 xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
254 xmlReader.setContentHandler(new XmlHandler());
255 logger.trace("Start Parser for optolink adapter");
256 xmlReader.parse(new InputSource(inStream));
258 } catch (IOException e) {
259 logger.trace("Connection error from optolink adapter");
260 } catch (SAXException e) {
261 logger.trace("XML Parser Error");
264 updateStatus(ThingStatus.OFFLINE);
267 if (!socket.isClosed()) {
270 } catch (Exception e) {
272 logger.trace("Connection to optolink adapter is died ... wait for restart");
275 private void startSocketReceiver() {
279 Thread thread = new Thread(socketReceiverRunnable);
280 thread.setName("VitotronicSocketThread");
285 private void sendSocketData(String message) {
287 logger.trace("Send Message {}", message);
289 if (message.matches("^set.*REFRESH$")) {
290 String[] msgParts = message.split(" ");
291 String[] thingChannel = msgParts[1].split(":");
292 message = "get " + thingChannel[0] + " " + thingChannel[1];
294 out.write((message + "\n").getBytes());
296 } catch (IOException e) {
297 logger.error("Error in sending data to optolink adapter");
298 logger.trace("Diagnostic: ", e);
302 // Handles all data what received from optolink adapter
304 public class XmlHandler implements ContentHandler {
309 boolean isDescription;
314 VitotronicThingHandler thingHandler;
315 Set<String> channels = new HashSet<>();
318 public void startElement(String uri, String localName, String pName, Attributes attr) throws SAXException {
323 updateStatus(ThingStatus.ONLINE);
332 isDescription = true;
337 thingType = attr.getValue("type");
339 thingID = attr.getValue("id");
341 thingHandler = thingHandlerMap.get(thingID);
345 channelID = attr.getValue("id");
347 channels.add(channelID);
349 if (thingHandler != null) {
350 logger.trace("Set Data for channel '{}' value '{}'", channelID, attr.getValue("value"));
351 thingHandler.setChannelValue(channelID, attr.getValue("value"));
356 } catch (Exception e) {
357 logger.error("Error in parsing data");
358 logger.trace("Diagnostic: ", e);
363 public void characters(char[] ch, int start, int length) throws SAXException {
365 description = new String(ch, start, length);
370 public void endElement(String uri, String localName, String qName) throws SAXException {
373 isDescription = false;
377 createThing(thingType, thingID);
388 // Unused function of xmlReader
390 public void endDocument() throws SAXException {
394 public void ignorableWhitespace(char[] arg0, int arg1, int arg2) throws SAXException {
398 public void processingInstruction(String arg0, String arg1) throws SAXException {
402 public void setDocumentLocator(Locator arg0) {
406 public void skippedEntity(String arg0) throws SAXException {
410 public void startDocument() throws SAXException {
414 public void startPrefixMapping(String arg0, String arg1) throws SAXException {
418 public void endPrefixMapping(String prefix) throws SAXException {