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.automower.internal.bridge;
15 import static org.openhab.binding.automower.internal.AutomowerBindingConstants.THING_TYPE_BRIDGE;
17 import java.util.Collections;
18 import java.util.Optional;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.eclipse.jetty.client.HttpClient;
26 import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.MowerListResult;
27 import org.openhab.binding.automower.internal.rest.exceptions.AutomowerCommunicationException;
28 import org.openhab.core.auth.client.oauth2.OAuthClientService;
29 import org.openhab.core.auth.client.oauth2.OAuthFactory;
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.ThingTypeUID;
35 import org.openhab.core.thing.binding.BaseBridgeHandler;
36 import org.openhab.core.types.Command;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
41 * The {@link AutomowerBridgeHandler} is responsible for handling commands, which are
42 * sent to one of the channels.
44 * @author Markus Pfleger - Initial contribution
47 public class AutomowerBridgeHandler extends BaseBridgeHandler {
48 private final Logger logger = LoggerFactory.getLogger(AutomowerBridgeHandler.class);
50 private static final String HUSQVARNA_API_TOKEN_URL = "https://api.authentication.husqvarnagroup.dev/v1/oauth2/token";
52 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
53 private static final long DEFAULT_POLLING_INTERVAL_S = TimeUnit.HOURS.toSeconds(1);
55 private final OAuthFactory oAuthFactory;
57 private @Nullable OAuthClientService oAuthService;
58 private @Nullable ScheduledFuture<?> automowerBridgePollingJob;
59 private @Nullable AutomowerBridge bridge;
60 private final HttpClient httpClient;
62 public AutomowerBridgeHandler(Bridge bridge, OAuthFactory oAuthFactory, HttpClient httpClient) {
64 this.oAuthFactory = oAuthFactory;
65 this.httpClient = httpClient;
68 private void pollAutomowers(AutomowerBridge bridge) {
69 MowerListResult automowers;
71 automowers = bridge.getAutomowers();
72 updateStatus(ThingStatus.ONLINE);
73 logger.debug("Found {} automowers", automowers.getData().size());
74 } catch (AutomowerCommunicationException e) {
75 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
76 "@text/comm-error-query-mowers-failed");
77 logger.warn("Unable to fetch automowers: {}", e.getMessage());
82 public void dispose() {
83 AutomowerBridge currentBridge = bridge;
84 if (currentBridge != null) {
85 stopAutomowerBridgePolling(currentBridge);
88 OAuthClientService oAuthService = this.oAuthService;
89 if (oAuthService != null) {
90 oAuthFactory.ungetOAuthService(thing.getUID().getAsString());
91 this.oAuthService = null;
96 public void initialize() {
97 AutomowerBridgeConfiguration bridgeConfiguration = getConfigAs(AutomowerBridgeConfiguration.class);
99 final String appKey = bridgeConfiguration.getAppKey();
100 final String appSecret = bridgeConfiguration.getAppSecret();
101 final Integer pollingIntervalS = bridgeConfiguration.getPollingInterval();
103 if (appKey == null || appKey.isEmpty()) {
104 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-no-app-key");
105 } else if (appSecret == null || appSecret.isEmpty()) {
106 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-no-app-secret");
107 } else if (pollingIntervalS != null && pollingIntervalS < 1) {
108 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
109 "@text/conf-error-invalid-polling-interval");
111 OAuthClientService oAuthService = oAuthFactory.createOAuthClientService(thing.getUID().getAsString(),
112 HUSQVARNA_API_TOKEN_URL, null, appKey, appSecret, null, null);
113 this.oAuthService = oAuthService;
115 if (bridge == null) {
116 AutomowerBridge currentBridge = new AutomowerBridge(oAuthService, appKey, httpClient, scheduler);
117 bridge = currentBridge;
118 startAutomowerBridgePolling(currentBridge, pollingIntervalS);
120 updateStatus(ThingStatus.UNKNOWN);
125 public void handleRemoval() {
126 oAuthFactory.deleteServiceAndAccessToken(thing.getUID().getAsString());
127 super.handleRemoval();
130 private void startAutomowerBridgePolling(AutomowerBridge bridge, @Nullable Integer pollingIntervalS) {
131 ScheduledFuture<?> currentPollingJob = automowerBridgePollingJob;
132 if (currentPollingJob == null) {
133 final long pollingIntervalToUse = pollingIntervalS == null ? DEFAULT_POLLING_INTERVAL_S : pollingIntervalS;
134 automowerBridgePollingJob = scheduler.scheduleWithFixedDelay(() -> pollAutomowers(bridge), 1,
135 pollingIntervalToUse, TimeUnit.SECONDS);
139 private void stopAutomowerBridgePolling(AutomowerBridge bridge) {
140 ScheduledFuture<?> currentPollingJob = automowerBridgePollingJob;
141 if (currentPollingJob != null) {
142 currentPollingJob.cancel(true);
143 automowerBridgePollingJob = null;
148 public void handleCommand(ChannelUID channelUID, Command command) {
151 public @Nullable AutomowerBridge getAutomowerBridge() {
155 public Optional<MowerListResult> getAutomowers() {
156 AutomowerBridge currentBridge = bridge;
157 if (currentBridge == null) {
158 return Optional.empty();
162 return Optional.of(currentBridge.getAutomowers());
163 } catch (AutomowerCommunicationException e) {
164 logger.debug("Bridge cannot get list of available automowers {}", e.getMessage());
165 return Optional.empty();