]> git.basschouten.com Git - openhab-addons.git/blob
dc1927641e0670349fffb8bdc5a195d8da29d021
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.wolfsmartset.internal.handler;
14
15 import static org.openhab.binding.wolfsmartset.internal.WolfSmartsetBindingConstants.CONFIG_SYSTEM_ID;
16
17 import java.util.Collection;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Set;
21 import java.util.concurrent.ConcurrentHashMap;
22 import java.util.concurrent.CopyOnWriteArraySet;
23 import java.util.concurrent.Future;
24 import java.util.concurrent.TimeUnit;
25 import java.util.concurrent.atomic.AtomicInteger;
26 import java.util.stream.Collectors;
27
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.eclipse.jetty.client.HttpClient;
31 import org.openhab.binding.wolfsmartset.internal.api.WolfSmartsetApi;
32 import org.openhab.binding.wolfsmartset.internal.api.WolfSmartsetCloudException;
33 import org.openhab.binding.wolfsmartset.internal.config.WolfSmartsetAccountConfiguration;
34 import org.openhab.binding.wolfsmartset.internal.discovery.WolfSmartsetAccountDiscoveryService;
35 import org.openhab.binding.wolfsmartset.internal.dto.GetGuiDescriptionForGatewayDTO;
36 import org.openhab.binding.wolfsmartset.internal.dto.GetSystemListDTO;
37 import org.openhab.core.thing.Bridge;
38 import org.openhab.core.thing.ChannelUID;
39 import org.openhab.core.thing.Thing;
40 import org.openhab.core.thing.ThingStatus;
41 import org.openhab.core.thing.ThingStatusDetail;
42 import org.openhab.core.thing.binding.BaseBridgeHandler;
43 import org.openhab.core.thing.binding.ThingHandler;
44 import org.openhab.core.thing.binding.ThingHandlerService;
45 import org.openhab.core.types.Command;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 /**
50  * The {@link WolfSmartsetAccountBridgeHandler} is responsible for managing
51  * communication with the WolfSmartset API.
52  *
53  * @author Bo Biene - Initial contribution
54  */
55 @NonNullByDefault
56 public class WolfSmartsetAccountBridgeHandler extends BaseBridgeHandler {
57     private static final int REFRESH_STARTUP_DELAY_SECONDS = 3;
58     private static final int REFRESH_INTERVAL_SECONDS = 1;
59     private static final int DEFAULT_REFRESH_INTERVAL_CONFIGURATION_MINUTES = 10;
60     private static final int DEFAULT_REFRESH_INTERVAL_VALUES_SECONDS = 15;
61
62     private final Logger logger = LoggerFactory.getLogger(WolfSmartsetAccountBridgeHandler.class);
63
64     private final HttpClient httpClient;
65
66     private @NonNullByDefault({}) WolfSmartsetApi api;
67     private int refreshIntervalStructureMinutes;
68     private int refreshIntervalValuesSeconds;
69     private boolean discoveryEnabled;
70     private @Nullable List<GetSystemListDTO> cachedSystems = null;
71
72     private final Map<String, WolfSmartsetSystemBridgeHandler> systemHandlers = new ConcurrentHashMap<>();
73     private final Set<String> systemIds = new CopyOnWriteArraySet<>();
74
75     private @Nullable Future<?> refreshSystemsJob;
76     private final AtomicInteger refreshConfigurationCounter = new AtomicInteger(REFRESH_STARTUP_DELAY_SECONDS);
77     private final AtomicInteger refreshValuesCounter = new AtomicInteger(REFRESH_STARTUP_DELAY_SECONDS);
78
79     public WolfSmartsetAccountBridgeHandler(final Bridge bridge, HttpClient httpClient) {
80         super(bridge);
81         this.httpClient = httpClient;
82     }
83
84     @Override
85     public void initialize() {
86         logger.debug("AccountBridge: Initializing");
87
88         WolfSmartsetAccountConfiguration config = getConfigAs(WolfSmartsetAccountConfiguration.class);
89
90         Integer value;
91         value = config.refreshIntervalStructure;
92         refreshIntervalStructureMinutes = value == null ? DEFAULT_REFRESH_INTERVAL_CONFIGURATION_MINUTES : value;
93
94         value = config.refreshIntervalValues;
95         refreshIntervalValuesSeconds = value == null ? DEFAULT_REFRESH_INTERVAL_VALUES_SECONDS : value;
96
97         String username = config.username;
98         String password = config.password;
99         username = username == null ? "" : username;
100         password = password == null ? "" : password;
101
102         Boolean booleanValue = config.discoveryEnabled;
103         discoveryEnabled = booleanValue == null ? false : booleanValue.booleanValue();
104         logger.debug("AccountBridge: System and unit discovery is {}", discoveryEnabled ? "enabled" : "disabled");
105         if (username.trim().isEmpty() || password.trim().isEmpty()) {
106             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Missing username or password");
107         } else {
108             try {
109                 api = new WolfSmartsetApi(username, password, httpClient, scheduler);
110                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "Checking authorization");
111                 scheduleRefreshJob();
112             } catch (WolfSmartsetCloudException e) {
113                 logger.error("unable to create wolf smartset api", e);
114                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
115             }
116         }
117     }
118
119     @Override
120     public void dispose() {
121         cancelRefreshJob();
122         api.stopRequestQueue();
123         logger.debug("AccountBridge: Disposing");
124     }
125
126     @Override
127     public Collection<Class<? extends ThingHandlerService>> getServices() {
128         return Set.of(WolfSmartsetAccountDiscoveryService.class);
129     }
130
131     @Override
132     public void handleCommand(ChannelUID channelUID, Command command) {
133     }
134
135     @Override
136     public void childHandlerInitialized(ThingHandler systemHandler, Thing systemThing) {
137         String systemId = (String) systemThing.getConfiguration().get(CONFIG_SYSTEM_ID);
138         systemHandlers.put(systemId, (WolfSmartsetSystemBridgeHandler) systemHandler);
139         systemIds.add(systemId);
140         scheduleRefreshJob();
141         logger.debug("AccountBridge: Adding system handler for {} with id {}", systemThing.getUID(), systemId);
142     }
143
144     @Override
145     public void childHandlerDisposed(ThingHandler systemHandler, Thing systemThing) {
146         String systemId = (String) systemThing.getConfiguration().get(CONFIG_SYSTEM_ID);
147         systemHandlers.remove(systemId);
148         systemIds.remove(systemId);
149         logger.debug("AccountBridge: Removing system handler for {} with id {}", systemThing.getUID(), systemId);
150     }
151
152     /**
153      * returns truee if BackgroundDiscoveryEnabled
154      */
155     public boolean isBackgroundDiscoveryEnabled() {
156         return discoveryEnabled;
157     }
158
159     /**
160      * returns the list of the GetSystemListDTO available
161      */
162     public @Nullable List<GetSystemListDTO> getRegisteredSystems() {
163         return cachedSystems;
164     }
165
166     /**
167      * force a full update of the wolf smartset cloud configuration
168      */
169     public void scheduleRefreshJob() {
170         logger.debug("AccountBridge: Scheduling system refresh job");
171         cancelRefreshJob();
172         refreshConfigurationCounter.set(0);
173         refreshValuesCounter.set(0);
174         refreshSystemsJob = scheduler.scheduleWithFixedDelay(this::refreshSystems, REFRESH_STARTUP_DELAY_SECONDS,
175                 REFRESH_INTERVAL_SECONDS, TimeUnit.SECONDS);
176     }
177
178     /**
179      * The refresh job updates the system channels on the refresh interval set in the system thing config.
180      * The system update process involves first running a system summary transaction to
181      * determine if any system data has changed since the last summary. If any change is detected,
182      * a full query of the systems is performed.
183      */
184     private void refreshSystems() {
185         if (refreshConfigurationCounter.getAndDecrement() == 0) {
186             refreshConfigurationCounter.set(refreshIntervalStructureMinutes * 60);
187             if (api.login()) {
188                 logger.debug("AccountBridge: refreshing configuration");
189                 updateStatus(ThingStatus.ONLINE);
190                 cachedSystems = api.getSystems();
191                 if (cachedSystems != null) {
192                     for (GetSystemListDTO system : api.getSystems()) {
193                         WolfSmartsetSystemBridgeHandler handler = systemHandlers.get(system.getId().toString());
194                         if (handler != null) {
195                             GetGuiDescriptionForGatewayDTO systemDescription = api.getSystemDescription(system.getId(),
196                                     system.getGatewayId());
197                             handler.updateConfiguration(system, systemDescription);
198                         }
199                     }
200                 }
201             } else {
202                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Authorization failed");
203             }
204         }
205
206         if (refreshValuesCounter.getAndDecrement() == 0) {
207             refreshValuesCounter.set(refreshIntervalValuesSeconds);
208             if (api.login()) {
209                 logger.debug("AccountBridge: refreshing values");
210                 updateStatus(ThingStatus.ONLINE);
211
212                 var systemConfigs = systemHandlers.values().stream().map(s -> s.getSystemConfig())
213                         .filter(s -> s != null).collect(Collectors.toSet());
214                 if (systemConfigs != null && !systemConfigs.isEmpty()) {
215                     var systemStates = api.getSystemState(systemConfigs);
216                     if (systemStates != null) {
217                         for (var systemState : systemStates) {
218                             if (systemState != null) {
219                                 var systemHandler = systemHandlers.get(systemState.getSystemId().toString());
220                                 if (systemHandler != null) {
221                                     systemHandler.updateSystemState(systemState);
222                                 }
223                             }
224                         }
225                     } else {
226                         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
227                                 "Failed to update system states");
228                     }
229
230                     for (var systemHandler : systemHandlers.values()) {
231                         if (systemHandler != null) {
232                             var systemConfig = systemHandler.getSystemConfig();
233                             if (systemConfig != null) {
234                                 var faultMessages = api.getFaultMessages(systemConfig.getId(),
235                                         systemConfig.getGatewayId());
236
237                                 systemHandler.updateFaultMessages(faultMessages);
238
239                                 for (var unitHandler : systemHandler.getUnitHandler()) {
240                                     if (unitHandler != null) {
241                                         var tabmenu = unitHandler.getTabMenu();
242                                         if (tabmenu != null) {
243                                             var lastRefreshTime = unitHandler.getLastRefreshTime();
244                                             var valueIds = tabmenu.parameterDescriptors.stream()
245                                                     .filter(p -> p.valueId > 0).map(p -> p.valueId)
246                                                     .collect(Collectors.toList());
247                                             var paramValues = api.getGetParameterValues(systemConfig.getId(),
248                                                     systemConfig.getGatewayId(), tabmenu.bundleId, valueIds,
249                                                     lastRefreshTime);
250
251                                             unitHandler.updateValues(paramValues);
252                                         }
253                                     }
254                                 }
255                             } else {
256                                 // waiting for config.
257                                 systemHandler.updateSystemState(null);
258                             }
259                         }
260                     }
261                 }
262             } else {
263                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Authorization failed");
264             }
265         }
266     }
267
268     private void cancelRefreshJob() {
269         Future<?> localRefreshSystemsJob = refreshSystemsJob;
270         if (localRefreshSystemsJob != null) {
271             localRefreshSystemsJob.cancel(true);
272             logger.debug("AccountBridge: Canceling system refresh job");
273         }
274     }
275 }