2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.wolfsmartset.internal.handler;
15 import static org.openhab.binding.wolfsmartset.internal.WolfSmartsetBindingConstants.CONFIG_SYSTEM_ID;
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.List;
22 import java.util.concurrent.ConcurrentHashMap;
23 import java.util.concurrent.CopyOnWriteArraySet;
24 import java.util.concurrent.Future;
25 import java.util.concurrent.TimeUnit;
26 import java.util.concurrent.atomic.AtomicInteger;
27 import java.util.stream.Collectors;
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.eclipse.jetty.client.HttpClient;
32 import org.openhab.binding.wolfsmartset.internal.api.WolfSmartsetApi;
33 import org.openhab.binding.wolfsmartset.internal.api.WolfSmartsetCloudException;
34 import org.openhab.binding.wolfsmartset.internal.config.WolfSmartsetAccountConfiguration;
35 import org.openhab.binding.wolfsmartset.internal.discovery.WolfSmartsetAccountDiscoveryService;
36 import org.openhab.binding.wolfsmartset.internal.dto.GetGuiDescriptionForGatewayDTO;
37 import org.openhab.binding.wolfsmartset.internal.dto.GetSystemListDTO;
38 import org.openhab.core.thing.Bridge;
39 import org.openhab.core.thing.ChannelUID;
40 import org.openhab.core.thing.Thing;
41 import org.openhab.core.thing.ThingStatus;
42 import org.openhab.core.thing.ThingStatusDetail;
43 import org.openhab.core.thing.binding.BaseBridgeHandler;
44 import org.openhab.core.thing.binding.ThingHandler;
45 import org.openhab.core.thing.binding.ThingHandlerService;
46 import org.openhab.core.types.Command;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
51 * The {@link WolfSmartsetAccountBridgeHandler} is responsible for managing
52 * communication with the WolfSmartset API.
54 * @author Bo Biene - Initial contribution
57 public class WolfSmartsetAccountBridgeHandler extends BaseBridgeHandler {
58 private static final int REFRESH_STARTUP_DELAY_SECONDS = 3;
59 private static final int REFRESH_INTERVAL_SECONDS = 1;
60 private static final int DEFAULT_REFRESH_INTERVAL_CONFIGURATION_MINUTES = 10;
61 private static final int DEFAULT_REFRESH_INTERVAL_VALUES_SECONDS = 15;
63 private final Logger logger = LoggerFactory.getLogger(WolfSmartsetAccountBridgeHandler.class);
65 private final HttpClient httpClient;
67 private @NonNullByDefault({}) WolfSmartsetApi api;
68 private int refreshIntervalStructureMinutes;
69 private int refreshIntervalValuesSeconds;
70 private boolean discoveryEnabled;
71 private @Nullable List<GetSystemListDTO> cachedSystems = null;
73 private final Map<String, WolfSmartsetSystemBridgeHandler> systemHandlers = new ConcurrentHashMap<>();
74 private final Set<String> systemIds = new CopyOnWriteArraySet<>();
76 private @Nullable Future<?> refreshSystemsJob;
77 private final AtomicInteger refreshConfigurationCounter = new AtomicInteger(REFRESH_STARTUP_DELAY_SECONDS);
78 private final AtomicInteger refreshValuesCounter = new AtomicInteger(REFRESH_STARTUP_DELAY_SECONDS);
80 public WolfSmartsetAccountBridgeHandler(final Bridge bridge, HttpClient httpClient) {
82 this.httpClient = httpClient;
86 public void initialize() {
87 logger.debug("AccountBridge: Initializing");
89 WolfSmartsetAccountConfiguration config = getConfigAs(WolfSmartsetAccountConfiguration.class);
92 value = config.refreshIntervalStructure;
93 refreshIntervalStructureMinutes = value == null ? DEFAULT_REFRESH_INTERVAL_CONFIGURATION_MINUTES : value;
95 value = config.refreshIntervalValues;
96 refreshIntervalValuesSeconds = value == null ? DEFAULT_REFRESH_INTERVAL_VALUES_SECONDS : value;
98 String username = config.username;
99 String password = config.password;
100 username = username == null ? "" : username;
101 password = password == null ? "" : password;
103 Boolean booleanValue = config.discoveryEnabled;
104 discoveryEnabled = booleanValue == null ? false : booleanValue.booleanValue();
105 logger.debug("AccountBridge: System and unit discovery is {}", discoveryEnabled ? "enabled" : "disabled");
106 if (username.trim().isEmpty() || password.trim().isEmpty()) {
107 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Missing username or password");
110 api = new WolfSmartsetApi(username, password, httpClient, scheduler);
111 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "Checking authorization");
112 scheduleRefreshJob();
113 } catch (WolfSmartsetCloudException e) {
114 logger.error("unable to create wolf smartset api", e);
115 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
121 public void dispose() {
123 api.stopRequestQueue();
124 logger.debug("AccountBridge: Disposing");
128 public Collection<Class<? extends ThingHandlerService>> getServices() {
129 return Collections.singleton(WolfSmartsetAccountDiscoveryService.class);
133 public void handleCommand(ChannelUID channelUID, Command command) {
137 public void childHandlerInitialized(ThingHandler systemHandler, Thing systemThing) {
138 String systemId = (String) systemThing.getConfiguration().get(CONFIG_SYSTEM_ID);
139 systemHandlers.put(systemId, (WolfSmartsetSystemBridgeHandler) systemHandler);
140 systemIds.add(systemId);
141 scheduleRefreshJob();
142 logger.debug("AccountBridge: Adding system handler for {} with id {}", systemThing.getUID(), systemId);
146 public void childHandlerDisposed(ThingHandler systemHandler, Thing systemThing) {
147 String systemId = (String) systemThing.getConfiguration().get(CONFIG_SYSTEM_ID);
148 systemHandlers.remove(systemId);
149 systemIds.remove(systemId);
150 logger.debug("AccountBridge: Removing system handler for {} with id {}", systemThing.getUID(), systemId);
154 * returns truee if BackgroundDiscoveryEnabled
156 public boolean isBackgroundDiscoveryEnabled() {
157 return discoveryEnabled;
161 * returns the list of the GetSystemListDTO available
163 public @Nullable List<GetSystemListDTO> getRegisteredSystems() {
164 return cachedSystems;
168 * force a full update of the wolf smartset cloud configuration
170 public void scheduleRefreshJob() {
171 logger.debug("AccountBridge: Scheduling system refresh job");
173 refreshConfigurationCounter.set(0);
174 refreshValuesCounter.set(0);
175 refreshSystemsJob = scheduler.scheduleWithFixedDelay(this::refreshSystems, REFRESH_STARTUP_DELAY_SECONDS,
176 REFRESH_INTERVAL_SECONDS, TimeUnit.SECONDS);
180 * The refresh job updates the system channels on the refresh interval set in the system thing config.
181 * The system update process involves first running a system summary transaction to
182 * determine if any system data has changed since the last summary. If any change is detected,
183 * a full query of the systems is performed.
185 private void refreshSystems() {
186 if (refreshConfigurationCounter.getAndDecrement() == 0) {
187 refreshConfigurationCounter.set(refreshIntervalStructureMinutes * 60);
189 logger.debug("AccountBridge: refreshing configuration");
190 updateStatus(ThingStatus.ONLINE);
191 cachedSystems = api.getSystems();
192 if (cachedSystems != null) {
193 for (GetSystemListDTO system : api.getSystems()) {
194 WolfSmartsetSystemBridgeHandler handler = systemHandlers.get(system.getId().toString());
195 if (handler != null) {
196 GetGuiDescriptionForGatewayDTO systemDescription = api.getSystemDescription(system.getId(),
197 system.getGatewayId());
198 handler.updateConfiguration(system, systemDescription);
203 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Authorization failed");
207 if (refreshValuesCounter.getAndDecrement() == 0) {
208 refreshValuesCounter.set(refreshIntervalValuesSeconds);
210 logger.debug("AccountBridge: refreshing values");
211 updateStatus(ThingStatus.ONLINE);
213 var systemConfigs = systemHandlers.values().stream().map(s -> s.getSystemConfig())
214 .filter(s -> s != null).collect(Collectors.toSet());
215 if (systemConfigs != null && systemConfigs.size() > 0) {
216 var systemStates = api.getSystemState(systemConfigs);
217 if (systemStates != null) {
218 for (var systemState : systemStates) {
219 if (systemState != null) {
220 var systemHandler = systemHandlers.get(systemState.getSystemId().toString());
221 if (systemHandler != null) {
222 systemHandler.updateSystemState(systemState);
227 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
228 "Failed to update system states");
231 for (var systemHandler : systemHandlers.values()) {
232 if (systemHandler != null) {
233 var systemConfig = systemHandler.getSystemConfig();
234 if (systemConfig != null) {
235 var faultMessages = api.getFaultMessages(systemConfig.getId(),
236 systemConfig.getGatewayId());
238 systemHandler.updateFaultMessages(faultMessages);
240 for (var unitHandler : systemHandler.getUnitHandler()) {
241 if (unitHandler != null) {
242 var tabmenu = unitHandler.getTabMenu();
243 if (tabmenu != null) {
244 var lastRefreshTime = unitHandler.getLastRefreshTime();
245 var valueIds = tabmenu.parameterDescriptors.stream()
246 .filter(p -> p.valueId > 0).map(p -> p.valueId)
247 .collect(Collectors.toList());
248 var paramValues = api.getGetParameterValues(systemConfig.getId(),
249 systemConfig.getGatewayId(), tabmenu.bundleId, valueIds,
252 unitHandler.updateValues(paramValues);
257 // waiting for config.
258 systemHandler.updateSystemState(null);
264 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Authorization failed");
269 private void cancelRefreshJob() {
270 Future<?> localRefreshSystemsJob = refreshSystemsJob;
271 if (localRefreshSystemsJob != null) {
272 localRefreshSystemsJob.cancel(true);
273 logger.debug("AccountBridge: Canceling system refresh job");