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.jeelink.internal;
15 import java.util.ArrayList;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.List;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
23 import java.util.concurrent.atomic.AtomicBoolean;
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;
43 * Handler for a JeeLink USB Receiver thing.
45 * @author Volker Bier - Initial contribution
47 public class JeeLinkHandler extends BaseBridgeHandler implements BridgeHandler, ConnectionListener {
48 private final Logger logger = LoggerFactory.getLogger(JeeLinkHandler.class);
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;
55 private JeeLinkConnection connection;
56 private AtomicBoolean connectionInitialized = new AtomicBoolean(false);
57 private ScheduledFuture<?> connectJob;
58 private ScheduledFuture<?> initJob;
60 private long lastReadingTime;
61 private ScheduledFuture<?> monitorJob;
63 public JeeLinkHandler(Bridge bridge, SerialPortManager serialPortManager) {
65 this.serialPortManager = serialPortManager;
69 public void initialize() {
70 JeeLinkConfig cfg = getConfig().as(JeeLinkConfig.class);
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);
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();
85 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
86 "Connection configuration incomplete");
91 public void connectionOpened() {
92 logger.debug("Connection to port {} opened.", connection.getPort());
94 updateStatus(ThingStatus.ONLINE);
96 if (connectJob != null) {
97 logger.debug("Connection to port {} established. Reconnect cancelled.", connection.getPort());
98 connectJob.cancel(true);
102 JeeLinkConfig cfg = getConfig().as(JeeLinkConfig.class);
103 initJob = scheduler.schedule(() -> {
104 intializeConnection();
105 }, cfg.initDelay, TimeUnit.SECONDS);
107 logger.debug("Init commands scheduled in {} seconds.", cfg.initDelay);
109 if (cfg.reconnectInterval > 0) {
110 monitorJob = scheduler.scheduleWithFixedDelay(new Runnable() {
111 private long lastMonitorTime;
115 if (getThing().getStatus() == ThingStatus.ONLINE && lastReadingTime < lastMonitorTime) {
116 logger.debug("Monitoring job for port {} detected missing readings. Triggering reconnect...",
117 connection.getPort());
119 connection.closeConnection();
120 updateStatus(ThingStatus.OFFLINE);
122 connection.openConnection();
124 lastMonitorTime = System.currentTimeMillis();
126 }, cfg.reconnectInterval, cfg.reconnectInterval, TimeUnit.SECONDS);
127 logger.debug("Monitoring job started.");
132 public void connectionClosed() {
133 logger.debug("Connection to port {} closed.", connection.getPort());
135 updateStatus(ThingStatus.OFFLINE);
136 connectionInitialized.set(false);
138 if (initJob != null) {
139 initJob.cancel(true);
141 if (monitorJob != null) {
142 monitorJob.cancel(true);
147 public void connectionAborted(String cause) {
148 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, cause);
150 if (monitorJob != null) {
151 monitorJob.cancel(true);
153 if (initJob != null) {
154 initJob.cancel(true);
156 connectionInitialized.set(false);
158 connectJob = scheduler.schedule(() -> {
159 connection.openConnection();
160 }, 10, TimeUnit.SECONDS);
161 logger.debug("Connection to port {} aborted ({}). Reconnect scheduled.", connection.getPort(), cause);
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<>();
170 // this is the first handler for this reading class => also setup converter
171 readingClassHandlerMap.put(h.getReadingClass(), handlers);
173 if (SensorDefinition.ALL_TYPE.equals(h.getSensorType())) {
174 converters.addAll(SensorDefinition.getDiscoveryConverters());
176 JeeLinkReadingConverter<?> c = SensorDefinition.getConverter(h.getSensorType());
179 sensorTypeConvertersMap.put(h.getSensorType(), c);
184 if (!handlers.contains(h)) {
185 logger.debug("Adding reading handler for class {}: {}", h.getReadingClass(), h);
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);
199 if (handlers.isEmpty()) {
200 // this was the last handler for this reading class => also remove converter
201 readingClassHandlerMap.remove(h.getReadingClass());
203 if (SensorDefinition.ALL_TYPE.equals(h.getSensorType())) {
204 converters.removeAll(SensorDefinition.getDiscoveryConverters());
206 JeeLinkReadingConverter<?> c = SensorDefinition.getConverter(h.getSensorType());
208 converters.remove(c);
217 public void handleCommand(ChannelUID channelUid, Command command) {
221 public void handleInput(String input) {
222 lastReadingTime = System.currentTimeMillis();
224 // try all associated converters to find the correct one
225 for (JeeLinkReadingConverter<?> c : converters) {
226 Reading r = c.createReading(input);
229 // this converter is responsible
230 intializeConnection();
232 // propagate to the appropriate sensor handler
233 synchronized (readingClassHandlerMap) {
234 Set<ReadingHandler<? extends Reading>> handlers = getAllHandlers(r.getClass());
236 for (ReadingHandler h : handlers) {
246 private Set<ReadingHandler<? extends Reading>> getAllHandlers(Class<? extends Reading> readingClass) {
247 Set<ReadingHandler<? extends Reading>> handlers = new HashSet<>();
249 Set<ReadingHandler<? extends Reading>> typeHandlers = readingClassHandlerMap.get(readingClass);
250 if (typeHandlers != null) {
251 handlers.addAll(typeHandlers);
253 Set<ReadingHandler<? extends Reading>> discoveryHandlers = readingClassHandlerMap.get(Reading.class);
254 if (discoveryHandlers != null) {
255 handlers.addAll(discoveryHandlers);
261 private void intializeConnection() {
262 if (!connectionInitialized.getAndSet(true)) {
263 JeeLinkConfig cfg = getConfig().as(JeeLinkConfig.class);
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);
274 public void dispose() {
275 if (connectJob != null) {
276 connectJob.cancel(true);
280 if (connection != null) {
281 connection.closeConnection();
287 public JeeLinkConnection getConnection() {