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