]> git.basschouten.com Git - openhab-addons.git/blob
7b4697818c33f7a02cb9164372a2d4486b03ab86
[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.jeelink.internal;
14
15 import java.util.ArrayList;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Set;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
23 import java.util.concurrent.atomic.AtomicBoolean;
24
25 import org.openhab.binding.jeelink.internal.config.JeeLinkConfig;
26 import org.openhab.binding.jeelink.internal.connection.ConnectionListener;
27 import org.openhab.binding.jeelink.internal.connection.JeeLinkConnection;
28 import org.openhab.binding.jeelink.internal.connection.JeeLinkSerialConnection;
29 import org.openhab.binding.jeelink.internal.connection.JeeLinkTcpConnection;
30 import org.openhab.core.io.transport.serial.SerialPortIdentifier;
31 import org.openhab.core.io.transport.serial.SerialPortManager;
32 import org.openhab.core.thing.Bridge;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.ThingStatus;
35 import org.openhab.core.thing.ThingStatusDetail;
36 import org.openhab.core.thing.binding.BaseBridgeHandler;
37 import org.openhab.core.thing.binding.BridgeHandler;
38 import org.openhab.core.types.Command;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 /**
43  * Handler for a JeeLink USB Receiver thing.
44  *
45  * @author Volker Bier - Initial contribution
46  */
47 public class JeeLinkHandler extends BaseBridgeHandler implements BridgeHandler, ConnectionListener {
48     private final Logger logger = LoggerFactory.getLogger(JeeLinkHandler.class);
49
50     private final List<JeeLinkReadingConverter<?>> converters = new ArrayList<>();
51     private final Map<String, JeeLinkReadingConverter<?>> sensorTypeConvertersMap = new HashMap<>();
52     private final Map<Class<?>, Set<ReadingHandler<? extends Reading>>> readingClassHandlerMap = new HashMap<>();
53     private final SerialPortManager serialPortManager;
54
55     private JeeLinkConnection connection;
56     private AtomicBoolean connectionInitialized = new AtomicBoolean(false);
57     private ScheduledFuture<?> connectJob;
58     private ScheduledFuture<?> initJob;
59
60     private long lastReadingTime;
61     private ScheduledFuture<?> monitorJob;
62
63     public JeeLinkHandler(Bridge bridge, SerialPortManager serialPortManager) {
64         super(bridge);
65         this.serialPortManager = serialPortManager;
66     }
67
68     @Override
69     public void initialize() {
70         JeeLinkConfig cfg = getConfig().as(JeeLinkConfig.class);
71
72         if (cfg.serialPort != null && cfg.baudRate != null) {
73             SerialPortIdentifier serialPortIdentifier = serialPortManager.getIdentifier(cfg.serialPort);
74             if (serialPortIdentifier == null) {
75                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
76                         "Port not found: " + cfg.serialPort);
77                 return;
78             }
79             connection = new JeeLinkSerialConnection(serialPortIdentifier, cfg.baudRate, this);
80             connection.openConnection();
81         } else if (cfg.ipAddress != null && cfg.port != null) {
82             connection = new JeeLinkTcpConnection(cfg.ipAddress + ":" + cfg.port, scheduler, this);
83             connection.openConnection();
84         } else {
85             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
86                     "Connection configuration incomplete");
87         }
88     }
89
90     @Override
91     public void connectionOpened() {
92         logger.debug("Connection to port {} opened.", connection.getPort());
93
94         updateStatus(ThingStatus.ONLINE);
95
96         if (connectJob != null) {
97             logger.debug("Connection to port {} established. Reconnect cancelled.", connection.getPort());
98             connectJob.cancel(true);
99             connectJob = null;
100         }
101
102         JeeLinkConfig cfg = getConfig().as(JeeLinkConfig.class);
103         initJob = scheduler.schedule(() -> {
104             intializeConnection();
105         }, cfg.initDelay, TimeUnit.SECONDS);
106
107         logger.debug("Init commands scheduled in {} seconds.", cfg.initDelay);
108
109         if (cfg.reconnectInterval > 0) {
110             monitorJob = scheduler.scheduleWithFixedDelay(new Runnable() {
111                 private long lastMonitorTime;
112
113                 @Override
114                 public void run() {
115                     if (getThing().getStatus() == ThingStatus.ONLINE && lastReadingTime < lastMonitorTime) {
116                         logger.debug("Monitoring job for port {} detected missing readings. Triggering reconnect...",
117                                 connection.getPort());
118
119                         connection.closeConnection();
120                         updateStatus(ThingStatus.OFFLINE);
121
122                         connection.openConnection();
123                     }
124                     lastMonitorTime = System.currentTimeMillis();
125                 }
126             }, cfg.reconnectInterval, cfg.reconnectInterval, TimeUnit.SECONDS);
127             logger.debug("Monitoring job started.");
128         }
129     }
130
131     @Override
132     public void connectionClosed() {
133         logger.debug("Connection to port {} closed.", connection.getPort());
134
135         updateStatus(ThingStatus.OFFLINE);
136         connectionInitialized.set(false);
137
138         if (initJob != null) {
139             initJob.cancel(true);
140         }
141         if (monitorJob != null) {
142             monitorJob.cancel(true);
143         }
144     }
145
146     @Override
147     public void connectionAborted(String cause) {
148         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, cause);
149
150         if (monitorJob != null) {
151             monitorJob.cancel(true);
152         }
153         if (initJob != null) {
154             initJob.cancel(true);
155         }
156         connectionInitialized.set(false);
157
158         connectJob = scheduler.schedule(() -> {
159             connection.openConnection();
160         }, 10, TimeUnit.SECONDS);
161         logger.debug("Connection to port {} aborted ({}). Reconnect scheduled.", connection.getPort(), cause);
162     }
163
164     public void addReadingHandler(ReadingHandler<? extends Reading> h) {
165         synchronized (readingClassHandlerMap) {
166             Set<ReadingHandler<? extends Reading>> handlers = readingClassHandlerMap.get(h.getReadingClass());
167             if (handlers == null) {
168                 handlers = new HashSet<>();
169
170                 // this is the first handler for this reading class => also setup converter
171                 readingClassHandlerMap.put(h.getReadingClass(), handlers);
172
173                 if (SensorDefinition.ALL_TYPE.equals(h.getSensorType())) {
174                     converters.addAll(SensorDefinition.getDiscoveryConverters());
175                 } else {
176                     JeeLinkReadingConverter<?> c = SensorDefinition.getConverter(h.getSensorType());
177                     if (c != null) {
178                         converters.add(c);
179                         sensorTypeConvertersMap.put(h.getSensorType(), c);
180                     }
181                 }
182             }
183
184             if (!handlers.contains(h)) {
185                 logger.debug("Adding reading handler for class {}: {}", h.getReadingClass(), h);
186
187                 handlers.add(h);
188             }
189         }
190     }
191
192     public void removeReadingHandler(ReadingHandler<? extends Reading> h) {
193         synchronized (readingClassHandlerMap) {
194             Set<ReadingHandler<? extends Reading>> handlers = readingClassHandlerMap.get(h.getReadingClass());
195             if (handlers != null) {
196                 logger.debug("Removing reading handler for class {}: {}", h.getReadingClass(), h);
197                 handlers.remove(h);
198
199                 if (handlers.isEmpty()) {
200                     // this was the last handler for this reading class => also remove converter
201                     readingClassHandlerMap.remove(h.getReadingClass());
202
203                     if (SensorDefinition.ALL_TYPE.equals(h.getSensorType())) {
204                         converters.removeAll(SensorDefinition.getDiscoveryConverters());
205                     } else {
206                         JeeLinkReadingConverter<?> c = SensorDefinition.getConverter(h.getSensorType());
207                         if (c != null) {
208                             converters.remove(c);
209                         }
210                     }
211                 }
212             }
213         }
214     }
215
216     @Override
217     public void handleCommand(ChannelUID channelUid, Command command) {
218     }
219
220     @Override
221     public void handleInput(String input) {
222         lastReadingTime = System.currentTimeMillis();
223
224         // try all associated converters to find the correct one
225         for (JeeLinkReadingConverter<?> c : converters) {
226             Reading r = c.createReading(input);
227
228             if (r != null) {
229                 // this converter is responsible
230                 intializeConnection();
231
232                 // propagate to the appropriate sensor handler
233                 synchronized (readingClassHandlerMap) {
234                     Set<ReadingHandler<? extends Reading>> handlers = getAllHandlers(r.getClass());
235
236                     for (ReadingHandler h : handlers) {
237                         h.handleReading(r);
238                     }
239                 }
240
241                 break;
242             }
243         }
244     }
245
246     private Set<ReadingHandler<? extends Reading>> getAllHandlers(Class<? extends Reading> readingClass) {
247         Set<ReadingHandler<? extends Reading>> handlers = new HashSet<>();
248
249         Set<ReadingHandler<? extends Reading>> typeHandlers = readingClassHandlerMap.get(readingClass);
250         if (typeHandlers != null) {
251             handlers.addAll(typeHandlers);
252         }
253         Set<ReadingHandler<? extends Reading>> discoveryHandlers = readingClassHandlerMap.get(Reading.class);
254         if (discoveryHandlers != null) {
255             handlers.addAll(discoveryHandlers);
256         }
257
258         return handlers;
259     }
260
261     private void intializeConnection() {
262         if (!connectionInitialized.getAndSet(true)) {
263             JeeLinkConfig cfg = getConfig().as(JeeLinkConfig.class);
264
265             String initCommands = cfg.initCommands;
266             if (initCommands != null && !initCommands.trim().isEmpty()) {
267                 logger.debug("Sending init commands for port {}: {}", connection.getPort(), initCommands);
268                 connection.sendCommands(initCommands);
269             }
270         }
271     }
272
273     @Override
274     public void dispose() {
275         if (connectJob != null) {
276             connectJob.cancel(true);
277             connectJob = null;
278         }
279
280         if (connection != null) {
281             connection.closeConnection();
282         }
283
284         super.dispose();
285     }
286
287     public JeeLinkConnection getConnection() {
288         return connection;
289     }
290 }