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.List;
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;
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;
50 * The {@link WolfSmartsetAccountBridgeHandler} is responsible for managing
51 * communication with the WolfSmartset API.
53 * @author Bo Biene - Initial contribution
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;
62 private final Logger logger = LoggerFactory.getLogger(WolfSmartsetAccountBridgeHandler.class);
64 private final HttpClient httpClient;
66 private @NonNullByDefault({}) WolfSmartsetApi api;
67 private int refreshIntervalStructureMinutes;
68 private int refreshIntervalValuesSeconds;
69 private boolean discoveryEnabled;
70 private @Nullable List<GetSystemListDTO> cachedSystems = null;
72 private final Map<String, WolfSmartsetSystemBridgeHandler> systemHandlers = new ConcurrentHashMap<>();
73 private final Set<String> systemIds = new CopyOnWriteArraySet<>();
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);
79 public WolfSmartsetAccountBridgeHandler(final Bridge bridge, HttpClient httpClient) {
81 this.httpClient = httpClient;
85 public void initialize() {
86 logger.debug("AccountBridge: Initializing");
88 WolfSmartsetAccountConfiguration config = getConfigAs(WolfSmartsetAccountConfiguration.class);
91 value = config.refreshIntervalStructure;
92 refreshIntervalStructureMinutes = value == null ? DEFAULT_REFRESH_INTERVAL_CONFIGURATION_MINUTES : value;
94 value = config.refreshIntervalValues;
95 refreshIntervalValuesSeconds = value == null ? DEFAULT_REFRESH_INTERVAL_VALUES_SECONDS : value;
97 String username = config.username;
98 String password = config.password;
99 username = username == null ? "" : username;
100 password = password == null ? "" : password;
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");
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());
120 public void dispose() {
122 api.stopRequestQueue();
123 logger.debug("AccountBridge: Disposing");
127 public Collection<Class<? extends ThingHandlerService>> getServices() {
128 return Set.of(WolfSmartsetAccountDiscoveryService.class);
132 public void handleCommand(ChannelUID channelUID, Command command) {
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);
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);
153 * returns truee if BackgroundDiscoveryEnabled
155 public boolean isBackgroundDiscoveryEnabled() {
156 return discoveryEnabled;
160 * returns the list of the GetSystemListDTO available
162 public @Nullable List<GetSystemListDTO> getRegisteredSystems() {
163 return cachedSystems;
167 * force a full update of the wolf smartset cloud configuration
169 public void scheduleRefreshJob() {
170 logger.debug("AccountBridge: Scheduling system refresh job");
172 refreshConfigurationCounter.set(0);
173 refreshValuesCounter.set(0);
174 refreshSystemsJob = scheduler.scheduleWithFixedDelay(this::refreshSystems, REFRESH_STARTUP_DELAY_SECONDS,
175 REFRESH_INTERVAL_SECONDS, TimeUnit.SECONDS);
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.
184 private void refreshSystems() {
185 if (refreshConfigurationCounter.getAndDecrement() == 0) {
186 refreshConfigurationCounter.set(refreshIntervalStructureMinutes * 60);
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);
202 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Authorization failed");
206 if (refreshValuesCounter.getAndDecrement() == 0) {
207 refreshValuesCounter.set(refreshIntervalValuesSeconds);
209 logger.debug("AccountBridge: refreshing values");
210 updateStatus(ThingStatus.ONLINE);
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);
226 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
227 "Failed to update system states");
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());
237 systemHandler.updateFaultMessages(faultMessages);
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,
251 unitHandler.updateValues(paramValues);
256 // waiting for config.
257 systemHandler.updateSystemState(null);
263 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Authorization failed");
268 private void cancelRefreshJob() {
269 Future<?> localRefreshSystemsJob = refreshSystemsJob;
270 if (localRefreshSystemsJob != null) {
271 localRefreshSystemsJob.cancel(true);
272 logger.debug("AccountBridge: Canceling system refresh job");