2 * Copyright (c) 2010-2024 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.List;
17 import java.util.Map.Entry;
18 import java.util.concurrent.ConcurrentHashMap;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.insteon.internal.InsteonBinding;
25 import org.openhab.binding.insteon.internal.config.InsteonNetworkConfiguration;
26 import org.openhab.binding.insteon.internal.device.InsteonAddress;
27 import org.openhab.binding.insteon.internal.discovery.InsteonDeviceDiscoveryService;
28 import org.openhab.binding.insteon.internal.utils.Utils;
29 import org.openhab.core.io.console.Console;
30 import org.openhab.core.io.transport.serial.SerialPortManager;
31 import org.openhab.core.thing.Bridge;
32 import org.openhab.core.thing.ChannelUID;
33 import org.openhab.core.thing.ThingStatus;
34 import org.openhab.core.thing.ThingStatusDetail;
35 import org.openhab.core.thing.ThingUID;
36 import org.openhab.core.thing.binding.BaseBridgeHandler;
37 import org.openhab.core.types.Command;
38 import org.openhab.core.types.State;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
43 * The {@link InsteonNetworkHandler} is responsible for handling commands, which are
44 * sent to one of the channels.
46 * @author Rob Nielsen - Initial contribution
49 public class InsteonNetworkHandler extends BaseBridgeHandler {
50 private static final int DRIVER_INITIALIZED_TIME_IN_SECONDS = 1;
51 private static final int LOG_DEVICE_STATISTICS_DELAY_IN_SECONDS = 600;
52 private static final int RETRY_DELAY_IN_SECONDS = 30;
53 private static final int SETTLE_TIME_IN_SECONDS = 5;
55 private final Logger logger = LoggerFactory.getLogger(InsteonNetworkHandler.class);
57 private @Nullable InsteonBinding insteonBinding;
58 private @Nullable InsteonDeviceDiscoveryService insteonDeviceDiscoveryService;
59 private @Nullable ScheduledFuture<?> driverInitializedJob = null;
60 private @Nullable ScheduledFuture<?> pollingJob = null;
61 private @Nullable ScheduledFuture<?> reconnectJob = null;
62 private @Nullable ScheduledFuture<?> settleJob = null;
63 private long lastInsteonDeviceCreatedTimestamp = 0;
64 private @Nullable SerialPortManager serialPortManager;
65 private Map<String, String> deviceInfo = new ConcurrentHashMap<>();
66 private Map<String, String> channelInfo = new ConcurrentHashMap<>();
68 public static ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
70 public InsteonNetworkHandler(Bridge bridge, @Nullable SerialPortManager serialPortManager) {
72 this.serialPortManager = serialPortManager;
76 public void handleCommand(ChannelUID channelUID, Command command) {
80 public void initialize() {
81 logger.debug("Starting Insteon bridge");
82 InsteonNetworkConfiguration config = getConfigAs(InsteonNetworkConfiguration.class);
84 SerialPortManager serialPortManager = this.serialPortManager;
85 if (serialPortManager == null) {
86 String msg = "Initialization failed, serial port manager is null.";
89 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
93 insteonBinding = new InsteonBinding(this, config, serialPortManager, scheduler);
94 updateStatus(ThingStatus.UNKNOWN);
96 // hold off on starting to poll until devices that already are defined as things are added.
97 // wait SETTLE_TIME_IN_SECONDS to start then check every second afterwards until it has been at
98 // least SETTLE_TIME_IN_SECONDS since last device was created.
99 settleJob = scheduler.scheduleWithFixedDelay(() -> {
100 // check to see if it has been at least SETTLE_TIME_IN_SECONDS since last device was created
101 if (System.currentTimeMillis() - lastInsteonDeviceCreatedTimestamp > SETTLE_TIME_IN_SECONDS * 1000) {
102 // settle time has expired start polling
103 InsteonBinding insteonBinding = this.insteonBinding;
104 if (insteonBinding != null && insteonBinding.startPolling()) {
105 pollingJob = scheduler.scheduleWithFixedDelay(() -> {
106 insteonBinding.logDeviceStatistics();
107 }, 0, LOG_DEVICE_STATISTICS_DELAY_IN_SECONDS, TimeUnit.SECONDS);
109 // wait until driver is initialized before setting network to ONLINE
110 driverInitializedJob = scheduler.scheduleWithFixedDelay(() -> {
111 if (insteonBinding.isDriverInitialized()) {
112 logger.debug("driver is initialized");
114 insteonBinding.setIsActive(true);
116 updateStatus(ThingStatus.ONLINE);
118 ScheduledFuture<?> driverInitializedJob = this.driverInitializedJob;
119 if (driverInitializedJob != null) {
120 driverInitializedJob.cancel(false);
121 this.driverInitializedJob = null;
124 logger.debug("driver is not initialized yet");
126 }, 0, DRIVER_INITIALIZED_TIME_IN_SECONDS, TimeUnit.SECONDS);
128 String msg = "Initialization failed, unable to start the Insteon bridge with the port '"
129 + Utils.redactPassword(config.getPort()) + "'.";
132 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
135 ScheduledFuture<?> settleJob = this.settleJob;
136 if (settleJob != null) {
137 settleJob.cancel(false);
138 this.settleJob = null;
141 }, SETTLE_TIME_IN_SECONDS, 1, TimeUnit.SECONDS);
145 public void dispose() {
146 logger.debug("Shutting down Insteon bridge");
148 ScheduledFuture<?> driverInitializedJob = this.driverInitializedJob;
149 if (driverInitializedJob != null) {
150 driverInitializedJob.cancel(true);
151 this.driverInitializedJob = null;
154 ScheduledFuture<?> pollingJob = this.pollingJob;
155 if (pollingJob != null) {
156 pollingJob.cancel(true);
157 this.pollingJob = null;
160 ScheduledFuture<?> reconnectJob = this.reconnectJob;
161 if (reconnectJob != null) {
162 reconnectJob.cancel(true);
163 this.reconnectJob = null;
166 ScheduledFuture<?> settleJob = this.settleJob;
167 if (settleJob != null) {
168 settleJob.cancel(true);
169 this.settleJob = null;
172 InsteonBinding insteonBinding = this.insteonBinding;
173 if (insteonBinding != null) {
174 insteonBinding.shutdown();
175 this.insteonBinding = null;
185 public void updateState(ChannelUID channelUID, State state) {
186 super.updateState(channelUID, state);
189 public void bindingDisconnected() {
190 reconnectJob = scheduler.scheduleWithFixedDelay(() -> {
191 InsteonBinding insteonBinding = this.insteonBinding;
192 if (insteonBinding != null && insteonBinding.reconnect()) {
193 updateStatus(ThingStatus.ONLINE);
194 ScheduledFuture<?> reconnectJob = this.reconnectJob;
195 if (reconnectJob != null) {
196 reconnectJob.cancel(false);
197 this.reconnectJob = null;
200 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Port disconnected.");
202 }, 0, RETRY_DELAY_IN_SECONDS, TimeUnit.SECONDS);
205 public void insteonDeviceWasCreated() {
206 lastInsteonDeviceCreatedTimestamp = System.currentTimeMillis();
209 public InsteonBinding getInsteonBinding() {
210 InsteonBinding insteonBinding = this.insteonBinding;
211 if (insteonBinding != null) {
212 return insteonBinding;
214 throw new IllegalArgumentException("insteon binding is null");
218 public void setInsteonDeviceDiscoveryService(InsteonDeviceDiscoveryService insteonDeviceDiscoveryService) {
219 this.insteonDeviceDiscoveryService = insteonDeviceDiscoveryService;
222 public void addMissingDevices(List<String> missing) {
223 scheduler.execute(() -> {
224 InsteonDeviceDiscoveryService insteonDeviceDiscoveryService = this.insteonDeviceDiscoveryService;
225 if (insteonDeviceDiscoveryService != null) {
226 insteonDeviceDiscoveryService.addInsteonDevices(missing, getThing().getUID());
231 public void deviceNotLinked(InsteonAddress addr) {
232 getThing().getThings().stream().forEach((thing) -> {
233 InsteonDeviceHandler handler = (InsteonDeviceHandler) thing.getHandler();
234 if (handler != null && addr.equals(handler.getInsteonAddress())) {
235 handler.deviceNotLinked();
241 public void displayDevices(Console console) {
242 display(console, deviceInfo);
245 public void displayChannels(Console console) {
246 display(console, channelInfo);
249 public void displayLocalDatabase(Console console) {
250 InsteonBinding insteonBinding = this.insteonBinding;
251 if (insteonBinding != null) {
252 Map<String, String> databaseInfo = insteonBinding.getDatabaseInfo();
253 console.println("local database contains " + databaseInfo.size() + " entries");
254 display(console, databaseInfo);
258 public void initialized(ThingUID uid, String msg) {
259 deviceInfo.put(uid.getAsString(), msg);
262 public void disposed(ThingUID uid) {
263 deviceInfo.remove(uid.getAsString());
266 public boolean isChannelLinked(ChannelUID uid) {
267 return channelInfo.containsKey(uid.getAsString());
270 public void linked(ChannelUID uid, String msg) {
271 channelInfo.put(uid.getAsString(), msg);
274 public void unlinked(ChannelUID uid) {
275 channelInfo.remove(uid.getAsString());
278 private void display(Console console, Map<String, String> info) {
279 info.entrySet().stream() //
280 .sorted(Entry.comparingByKey()) //
281 .map(Entry::getValue) //
282 .forEach(console::println);