2 * Copyright (c) 2010-2020 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.insteon.internal.handler;
15 import java.util.ArrayList;
16 import java.util.Collections;
17 import java.util.List;
19 import java.util.concurrent.ConcurrentHashMap;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.insteon.internal.InsteonBinding;
26 import org.openhab.binding.insteon.internal.config.InsteonNetworkConfiguration;
27 import org.openhab.binding.insteon.internal.discovery.InsteonDeviceDiscoveryService;
28 import org.openhab.core.io.console.Console;
29 import org.openhab.core.io.transport.serial.SerialPortManager;
30 import org.openhab.core.thing.Bridge;
31 import org.openhab.core.thing.ChannelUID;
32 import org.openhab.core.thing.ThingStatus;
33 import org.openhab.core.thing.ThingStatusDetail;
34 import org.openhab.core.thing.ThingUID;
35 import org.openhab.core.thing.binding.BaseBridgeHandler;
36 import org.openhab.core.types.Command;
37 import org.openhab.core.types.State;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
42 * The {@link InsteonNetworkHandler} is responsible for handling commands, which are
43 * sent to one of the channels.
45 * @author Rob Nielsen - Initial contribution
48 @SuppressWarnings("null")
49 public class InsteonNetworkHandler extends BaseBridgeHandler {
50 private static final int LOG_DEVICE_STATISTICS_DELAY_IN_SECONDS = 600;
51 private static final int RETRY_DELAY_IN_SECONDS = 30;
52 private static final int SETTLE_TIME_IN_SECONDS = 5;
54 private final Logger logger = LoggerFactory.getLogger(InsteonNetworkHandler.class);
56 private @Nullable InsteonNetworkConfiguration config;
57 private @Nullable InsteonBinding insteonBinding;
58 private @Nullable InsteonDeviceDiscoveryService insteonDeviceDiscoveryService;
59 private @Nullable ScheduledFuture<?> pollingJob = null;
60 private @Nullable ScheduledFuture<?> reconnectJob = null;
61 private @Nullable ScheduledFuture<?> settleJob = null;
62 private long lastInsteonDeviceCreatedTimestamp = 0;
63 private @Nullable SerialPortManager serialPortManager;
64 private Map<String, String> deviceInfo = new ConcurrentHashMap<>();
65 private Map<String, String> channelInfo = new ConcurrentHashMap<>();
67 public static ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
69 public InsteonNetworkHandler(Bridge bridge, @Nullable SerialPortManager serialPortManager) {
71 this.serialPortManager = serialPortManager;
75 public void handleCommand(ChannelUID channelUID, Command command) {
79 public void initialize() {
80 logger.debug("Starting Insteon bridge");
81 config = getConfigAs(InsteonNetworkConfiguration.class);
82 updateStatus(ThingStatus.UNKNOWN);
84 scheduler.execute(() -> {
85 insteonBinding = new InsteonBinding(this, config, serialPortManager, scheduler);
87 // hold off on starting to poll until devices that already are defined as things are added.
88 // wait SETTLE_TIME_IN_SECONDS to start then check every second afterwards until it has been at
89 // least SETTLE_TIME_IN_SECONDS since last device was created.
90 settleJob = scheduler.scheduleWithFixedDelay(() -> {
91 // check to see if it has been at least SETTLE_TIME_IN_SECONDS since last device was created
92 if (System.currentTimeMillis() - lastInsteonDeviceCreatedTimestamp > SETTLE_TIME_IN_SECONDS * 1000) {
93 // settle time has expired start polling
94 if (insteonBinding.startPolling()) {
95 pollingJob = scheduler.scheduleWithFixedDelay(() -> {
96 insteonBinding.logDeviceStatistics();
97 }, 0, LOG_DEVICE_STATISTICS_DELAY_IN_SECONDS, TimeUnit.SECONDS);
99 insteonBinding.setIsActive(true);
101 updateStatus(ThingStatus.ONLINE);
103 String msg = "Initialization failed, unable to start the Insteon bridge with the port '"
104 + config.getPort() + "'.";
107 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
110 settleJob.cancel(false);
113 }, SETTLE_TIME_IN_SECONDS, 1, TimeUnit.SECONDS);
118 public void dispose() {
119 logger.debug("Shutting down Insteon bridge");
121 if (pollingJob != null) {
122 pollingJob.cancel(true);
126 if (reconnectJob != null) {
127 reconnectJob.cancel(true);
131 if (settleJob != null) {
132 settleJob.cancel(true);
136 if (insteonBinding != null) {
137 insteonBinding.shutdown();
138 insteonBinding = null;
148 public void updateState(ChannelUID channelUID, State state) {
149 super.updateState(channelUID, state);
152 public void bindingDisconnected() {
153 reconnectJob = scheduler.scheduleWithFixedDelay(() -> {
154 if (insteonBinding.reconnect()) {
155 updateStatus(ThingStatus.ONLINE);
156 reconnectJob.cancel(false);
159 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Port disconnected.");
161 }, 0, RETRY_DELAY_IN_SECONDS, TimeUnit.SECONDS);
164 public void insteonDeviceWasCreated() {
165 lastInsteonDeviceCreatedTimestamp = System.currentTimeMillis();
168 public @Nullable InsteonBinding getInsteonBinding() {
169 return insteonBinding;
172 public void setInsteonDeviceDiscoveryService(InsteonDeviceDiscoveryService insteonDeviceDiscoveryService) {
173 this.insteonDeviceDiscoveryService = insteonDeviceDiscoveryService;
176 public void addMissingDevices(List<String> missing) {
177 scheduler.execute(() -> {
178 insteonDeviceDiscoveryService.addInsteonDevices(missing, getThing().getUID());
182 public void displayDevices(Console console) {
183 display(console, deviceInfo);
186 public void displayChannels(Console console) {
187 display(console, channelInfo);
190 public void displayLocalDatabase(Console console) {
191 Map<String, String> databaseInfo = insteonBinding.getDatabaseInfo();
192 console.println("local database contains " + databaseInfo.size() + " entries");
193 display(console, databaseInfo);
196 public void initialized(ThingUID uid, String msg) {
197 deviceInfo.put(uid.getAsString(), msg);
200 public void disposed(ThingUID uid) {
201 deviceInfo.remove(uid.getAsString());
204 public boolean isChannelLinked(ChannelUID uid) {
205 return channelInfo.containsKey(uid.getAsString());
208 public void linked(ChannelUID uid, String msg) {
209 channelInfo.put(uid.getAsString(), msg);
212 public void unlinked(ChannelUID uid) {
213 channelInfo.remove(uid.getAsString());
216 private void display(Console console, Map<String, String> info) {
217 ArrayList<String> ids = new ArrayList<>(info.keySet());
218 Collections.sort(ids);
219 for (String id : ids) {
220 console.println(info.get(id));