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