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