]> git.basschouten.com Git - openhab-addons.git/blob
acff1898fc39145ab9dfe7ba14d25a9fc7f718a2
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.insteon.internal.handler;
14
15 import java.util.ArrayList;
16 import java.util.Collections;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.concurrent.ConcurrentHashMap;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22
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;
40
41 /**
42  * The {@link InsteonNetworkHandler} is responsible for handling commands, which are
43  * sent to one of the channels.
44  *
45  * @author Rob Nielsen - Initial contribution
46  */
47 @NonNullByDefault
48 public class InsteonNetworkHandler extends BaseBridgeHandler {
49     private static final int LOG_DEVICE_STATISTICS_DELAY_IN_SECONDS = 600;
50     private static final int RETRY_DELAY_IN_SECONDS = 30;
51     private static final int SETTLE_TIME_IN_SECONDS = 5;
52
53     private final Logger logger = LoggerFactory.getLogger(InsteonNetworkHandler.class);
54
55     private @Nullable InsteonNetworkConfiguration config;
56     private @Nullable InsteonBinding insteonBinding;
57     private @Nullable InsteonDeviceDiscoveryService insteonDeviceDiscoveryService;
58     private @Nullable ScheduledFuture<?> pollingJob = null;
59     private @Nullable ScheduledFuture<?> reconnectJob = null;
60     private @Nullable ScheduledFuture<?> settleJob = null;
61     private long lastInsteonDeviceCreatedTimestamp = 0;
62     private @Nullable SerialPortManager serialPortManager;
63     private Map<String, String> deviceInfo = new ConcurrentHashMap<>();
64     private Map<String, String> channelInfo = new ConcurrentHashMap<>();
65
66     public static ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
67
68     public InsteonNetworkHandler(Bridge bridge, @Nullable SerialPortManager serialPortManager) {
69         super(bridge);
70         this.serialPortManager = serialPortManager;
71     }
72
73     @Override
74     public void handleCommand(ChannelUID channelUID, Command command) {
75     }
76
77     @Override
78     public void initialize() {
79         logger.debug("Starting Insteon bridge");
80         config = getConfigAs(InsteonNetworkConfiguration.class);
81         updateStatus(ThingStatus.UNKNOWN);
82
83         scheduler.execute(() -> {
84             InsteonNetworkConfiguration config = this.config;
85             if (config == null) {
86                 String msg = "Initialization failed, configuration is null.";
87                 logger.warn(msg);
88
89                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
90                 return;
91             }
92             SerialPortManager serialPortManager = this.serialPortManager;
93             if (serialPortManager == null) {
94                 String msg = "Initialization failed, serial port manager is null.";
95                 logger.warn(msg);
96
97                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
98
99                 return;
100             }
101             insteonBinding = new InsteonBinding(this, config, serialPortManager, scheduler);
102
103             // hold off on starting to poll until devices that already are defined as things are added.
104             // wait SETTLE_TIME_IN_SECONDS to start then check every second afterwards until it has been at
105             // least SETTLE_TIME_IN_SECONDS since last device was created.
106             settleJob = scheduler.scheduleWithFixedDelay(() -> {
107                 // check to see if it has been at least SETTLE_TIME_IN_SECONDS since last device was created
108                 if (System.currentTimeMillis() - lastInsteonDeviceCreatedTimestamp > SETTLE_TIME_IN_SECONDS * 1000) {
109                     // settle time has expired start polling
110                     InsteonBinding insteonBinding = this.insteonBinding;
111                     if (insteonBinding != null && insteonBinding.startPolling()) {
112                         pollingJob = scheduler.scheduleWithFixedDelay(() -> {
113                             insteonBinding.logDeviceStatistics();
114                         }, 0, LOG_DEVICE_STATISTICS_DELAY_IN_SECONDS, TimeUnit.SECONDS);
115
116                         insteonBinding.setIsActive(true);
117
118                         updateStatus(ThingStatus.ONLINE);
119                     } else {
120                         String msg = "Initialization failed, unable to start the Insteon bridge with the port '"
121                                 + config.getPort() + "'.";
122                         logger.warn(msg);
123
124                         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
125                     }
126
127                     ScheduledFuture<?> settleJob = this.settleJob;
128                     if (settleJob != null) {
129                         settleJob.cancel(false);
130                     }
131                     this.settleJob = null;
132                 }
133             }, SETTLE_TIME_IN_SECONDS, 1, TimeUnit.SECONDS);
134         });
135     }
136
137     @Override
138     public void dispose() {
139         logger.debug("Shutting down Insteon bridge");
140
141         ScheduledFuture<?> pollingJob = this.pollingJob;
142         if (pollingJob != null) {
143             pollingJob.cancel(true);
144             this.pollingJob = null;
145         }
146
147         ScheduledFuture<?> reconnectJob = this.reconnectJob;
148         if (reconnectJob != null) {
149             reconnectJob.cancel(true);
150             this.reconnectJob = null;
151         }
152
153         ScheduledFuture<?> settleJob = this.settleJob;
154         if (settleJob != null) {
155             settleJob.cancel(true);
156             this.settleJob = null;
157         }
158
159         InsteonBinding insteonBinding = this.insteonBinding;
160         if (insteonBinding != null) {
161             insteonBinding.shutdown();
162             this.insteonBinding = null;
163         }
164
165         deviceInfo.clear();
166         channelInfo.clear();
167
168         super.dispose();
169     }
170
171     @Override
172     public void updateState(ChannelUID channelUID, State state) {
173         super.updateState(channelUID, state);
174     }
175
176     public void bindingDisconnected() {
177         reconnectJob = scheduler.scheduleWithFixedDelay(() -> {
178             InsteonBinding insteonBinding = this.insteonBinding;
179             if (insteonBinding != null && insteonBinding.reconnect()) {
180                 updateStatus(ThingStatus.ONLINE);
181                 ScheduledFuture<?> reconnectJob = this.reconnectJob;
182                 if (reconnectJob != null) {
183                     reconnectJob.cancel(false);
184                 }
185                 this.reconnectJob = null;
186             } else {
187                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Port disconnected.");
188             }
189         }, 0, RETRY_DELAY_IN_SECONDS, TimeUnit.SECONDS);
190     }
191
192     public void insteonDeviceWasCreated() {
193         lastInsteonDeviceCreatedTimestamp = System.currentTimeMillis();
194     }
195
196     public InsteonBinding getInsteonBinding() {
197         InsteonBinding insteonBinding = this.insteonBinding;
198         if (insteonBinding != null) {
199             return insteonBinding;
200         } else {
201             throw new IllegalArgumentException("insteon binding is null");
202         }
203     }
204
205     public void setInsteonDeviceDiscoveryService(InsteonDeviceDiscoveryService insteonDeviceDiscoveryService) {
206         this.insteonDeviceDiscoveryService = insteonDeviceDiscoveryService;
207     }
208
209     public void addMissingDevices(List<String> missing) {
210         scheduler.execute(() -> {
211             InsteonDeviceDiscoveryService insteonDeviceDiscoveryService = this.insteonDeviceDiscoveryService;
212             if (insteonDeviceDiscoveryService != null) {
213                 insteonDeviceDiscoveryService.addInsteonDevices(missing, getThing().getUID());
214             }
215         });
216     }
217
218     public void displayDevices(Console console) {
219         display(console, deviceInfo);
220     }
221
222     public void displayChannels(Console console) {
223         display(console, channelInfo);
224     }
225
226     public void displayLocalDatabase(Console console) {
227         InsteonBinding insteonBinding = this.insteonBinding;
228         if (insteonBinding != null) {
229             Map<String, String> databaseInfo = insteonBinding.getDatabaseInfo();
230             console.println("local database contains " + databaseInfo.size() + " entries");
231             display(console, databaseInfo);
232         }
233     }
234
235     public void initialized(ThingUID uid, String msg) {
236         deviceInfo.put(uid.getAsString(), msg);
237     }
238
239     public void disposed(ThingUID uid) {
240         deviceInfo.remove(uid.getAsString());
241     }
242
243     public boolean isChannelLinked(ChannelUID uid) {
244         return channelInfo.containsKey(uid.getAsString());
245     }
246
247     public void linked(ChannelUID uid, String msg) {
248         channelInfo.put(uid.getAsString(), msg);
249     }
250
251     public void unlinked(ChannelUID uid) {
252         channelInfo.remove(uid.getAsString());
253     }
254
255     private void display(Console console, Map<String, String> info) {
256         ArrayList<String> ids = new ArrayList<>(info.keySet());
257         Collections.sort(ids);
258         for (String id : ids) {
259             console.println(info.get(id));
260         }
261     }
262 }