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.homeconnect.internal.servlet;
15 import static java.nio.charset.StandardCharsets.UTF_8;
16 import static java.time.ZonedDateTime.now;
17 import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
18 import static org.openhab.binding.homeconnect.internal.HomeConnectBindingConstants.*;
20 import java.io.IOException;
21 import java.time.ZonedDateTime;
22 import java.time.format.DateTimeFormatter;
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.List;
27 import java.util.Optional;
29 import java.util.concurrent.CopyOnWriteArraySet;
30 import java.util.stream.Collectors;
32 import javax.servlet.ServletException;
33 import javax.servlet.http.HttpServlet;
34 import javax.servlet.http.HttpServletRequest;
35 import javax.servlet.http.HttpServletResponse;
36 import javax.ws.rs.core.MediaType;
38 import org.eclipse.jdt.annotation.NonNullByDefault;
39 import org.eclipse.jdt.annotation.Nullable;
40 import org.eclipse.jetty.http.HttpStatus;
41 import org.openhab.binding.homeconnect.internal.client.exception.ApplianceOfflineException;
42 import org.openhab.binding.homeconnect.internal.client.exception.AuthorizationException;
43 import org.openhab.binding.homeconnect.internal.client.exception.CommunicationException;
44 import org.openhab.binding.homeconnect.internal.client.model.ApiRequest;
45 import org.openhab.binding.homeconnect.internal.handler.AbstractHomeConnectThingHandler;
46 import org.openhab.binding.homeconnect.internal.handler.HomeConnectBridgeHandler;
47 import org.openhab.core.OpenHAB;
48 import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
49 import org.openhab.core.auth.client.oauth2.OAuthException;
50 import org.openhab.core.auth.client.oauth2.OAuthResponseException;
51 import org.osgi.framework.FrameworkUtil;
52 import org.osgi.service.component.annotations.Activate;
53 import org.osgi.service.component.annotations.Component;
54 import org.osgi.service.component.annotations.Deactivate;
55 import org.osgi.service.component.annotations.Reference;
56 import org.osgi.service.component.annotations.ServiceScope;
57 import org.osgi.service.http.HttpService;
58 import org.osgi.service.http.NamespaceException;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61 import org.thymeleaf.TemplateEngine;
62 import org.thymeleaf.context.WebContext;
63 import org.thymeleaf.extras.java8time.dialect.Java8TimeDialect;
64 import org.thymeleaf.templatemode.TemplateMode;
65 import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
67 import com.google.gson.Gson;
68 import com.google.gson.GsonBuilder;
69 import com.google.gson.JsonPrimitive;
70 import com.google.gson.JsonSerializer;
74 * Home Connect servlet.
76 * @author Jonas BrĂ¼stel - Initial Contribution
79 @Component(service = HomeConnectServlet.class, scope = ServiceScope.SINGLETON, immediate = true)
80 public class HomeConnectServlet extends HttpServlet {
82 private static final String SLASH = "/";
83 private static final String SERVLET_NAME = "homeconnect";
84 private static final String SERVLET_PATH = SLASH + SERVLET_NAME;
85 private static final String ASSETS_PATH = SERVLET_PATH + "/asset";
86 private static final String ROOT_PATH = SLASH;
87 private static final String APPLIANCES_PATH = "/appliances";
88 private static final String REQUEST_LOG_PATH = "/log/requests";
89 private static final String EVENT_LOG_PATH = "/log/events";
90 private static final String DEFAULT_CONTENT_TYPE = "text/html; charset=UTF-8";
91 private static final String PARAM_CODE = "code";
92 private static final String PARAM_STATE = "state";
93 private static final String PARAM_EXPORT = "export";
94 private static final String PARAM_ACTION = "action";
95 private static final String PARAM_BRIDGE_ID = "bridgeId";
96 private static final String PARAM_THING_ID = "thingId";
97 private static final String PARAM_PATH = "path";
98 private static final String PARAM_REDIRECT_URI = "redirectUri";
99 private static final String ACTION_AUTHORIZE = "authorize";
100 private static final String ACTION_CLEAR_CREDENTIALS = "clearCredentials";
101 private static final String ACTION_SHOW_DETAILS = "show-details";
102 private static final String ACTION_ALL_PROGRAMS = "all-programs";
103 private static final String ACTION_AVAILABLE_PROGRAMS = "available-programs";
104 private static final String ACTION_SELECTED_PROGRAM = "selected-program";
105 private static final String ACTION_ACTIVE_PROGRAM = "active-program";
106 private static final String ACTION_OPERATION_STATE = "operation-state";
107 private static final String ACTION_POWER_STATE = "power-state";
108 private static final String ACTION_DOOR_STATE = "door-state";
109 private static final String ACTION_REMOTE_START_ALLOWED = "remote-control-start-allowed";
110 private static final String ACTION_REMOTE_CONTROL_ACTIVE = "remote-control-active";
111 private static final String ACTION_PUT_RAW = "put-raw";
112 private static final String ACTION_GET_RAW = "get-raw";
113 private static final DateTimeFormatter FILE_EXPORT_DTF = ISO_OFFSET_DATE_TIME;
114 private static final String EMPTY_RESPONSE = "{}";
115 private static final long serialVersionUID = -2449763690208703307L;
117 private final Logger logger = LoggerFactory.getLogger(HomeConnectServlet.class);
118 private final HttpService httpService;
119 private final TemplateEngine templateEngine;
120 private final Set<HomeConnectBridgeHandler> bridgeHandlers;
121 private final Gson gson;
124 public HomeConnectServlet(@Reference HttpService httpService) {
125 bridgeHandlers = new CopyOnWriteArraySet<>();
126 gson = new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, (JsonSerializer<ZonedDateTime>) (src,
127 typeOfSrc, context) -> new JsonPrimitive(src.format(DateTimeFormatter.ISO_DATE_TIME))).create();
128 this.httpService = httpService;
132 logger.debug("Initialize Home Connect configuration servlet ({})", SERVLET_PATH);
133 httpService.registerServlet(SERVLET_PATH, this, null, httpService.createDefaultHttpContext());
134 httpService.registerResources(ASSETS_PATH, "assets", null);
135 } catch (ServletException | NamespaceException e) {
136 logger.warn("Could not register Home Connect servlet! ({})", SERVLET_PATH, e);
139 // setup template engine
140 ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(getServletContext());
141 templateResolver.setTemplateMode(TemplateMode.HTML);
142 templateResolver.setPrefix("/templates/");
143 templateResolver.setSuffix(".html");
144 templateResolver.setCacheable(true);
145 templateEngine = new TemplateEngine();
146 templateEngine.addDialect(new Java8TimeDialect());
147 templateEngine.setTemplateResolver(templateResolver);
151 protected void dispose() {
152 httpService.unregister(SERVLET_PATH);
153 httpService.unregister(ASSETS_PATH);
157 protected void doGet(@Nullable HttpServletRequest request, @Nullable HttpServletResponse response)
159 if (request == null || response == null) {
162 response.setContentType(DEFAULT_CONTENT_TYPE);
163 response.setCharacterEncoding(UTF_8.name());
165 String path = request.getPathInfo();
166 if (path == null || path.isEmpty() || path.equals(ROOT_PATH)) {
167 String code = request.getParameter(PARAM_CODE);
168 String state = request.getParameter(PARAM_STATE);
169 if (code != null && state != null && !code.trim().isEmpty() && !state.trim().isEmpty()) {
170 getBridgeAuthenticationPage(request, response, code, state);
172 getBridgesPage(request, response);
174 } else if (pathMatches(path, APPLIANCES_PATH)) {
175 String action = request.getParameter(PARAM_ACTION);
176 String thingId = request.getParameter(PARAM_THING_ID);
177 if (action != null && thingId != null && !action.trim().isEmpty() && !thingId.trim().isEmpty()) {
178 processApplianceActions(response, action, thingId);
180 getAppliancesPage(request, response);
182 } else if (pathMatches(path, REQUEST_LOG_PATH)) {
183 String export = request.getParameter(PARAM_EXPORT);
184 String bridgeId = request.getParameter(PARAM_BRIDGE_ID);
185 if (export != null && bridgeId != null && !export.trim().isEmpty() && !bridgeId.trim().isEmpty()) {
186 getRequestLogExport(response, bridgeId);
188 getRequestLogPage(request, response);
190 } else if (pathMatches(path, EVENT_LOG_PATH)) {
191 String export = request.getParameter(PARAM_EXPORT);
192 String bridgeId = request.getParameter(PARAM_BRIDGE_ID);
193 if (export != null && bridgeId != null && !export.trim().isEmpty() && !bridgeId.trim().isEmpty()) {
194 getEventLogExport(response, bridgeId);
196 getEventLogPage(request, response);
199 response.sendError(HttpServletResponse.SC_NOT_FOUND);
204 protected void doPost(@Nullable HttpServletRequest request, @Nullable HttpServletResponse response)
206 if (request == null || response == null) {
209 response.setContentType("text/html; charset=UTF-8");
210 response.setCharacterEncoding("UTF-8");
212 String path = request.getPathInfo();
213 if (path == null || path.isEmpty() || path.equals(ROOT_PATH)) {
214 if (request.getParameter(PARAM_ACTION) != null && request.getParameter(PARAM_BRIDGE_ID) != null) {
215 postBridgesPage(request, response);
217 response.sendError(HttpServletResponse.SC_NOT_FOUND);
219 } else if (pathMatches(path, APPLIANCES_PATH)) {
220 String requestPayload = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
221 String action = request.getParameter(PARAM_ACTION);
222 String thingId = request.getParameter(PARAM_THING_ID);
223 String targetPath = request.getParameter(PARAM_PATH);
225 if ((ACTION_PUT_RAW.equals(action) || ACTION_GET_RAW.equals(action)) && thingId != null
226 && targetPath != null && action != null) {
227 processRawApplianceActions(response, action, thingId, targetPath, requestPayload);
229 response.sendError(HttpServletResponse.SC_NOT_FOUND);
232 response.sendError(HttpServletResponse.SC_NOT_FOUND);
237 * Add Home Connect bridge handler to configuration servlet, to allow user to authenticate against Home Connect API.
239 * @param bridgeHandler bridge handler
241 public void addBridgeHandler(HomeConnectBridgeHandler bridgeHandler) {
242 bridgeHandlers.add(bridgeHandler);
246 * Remove Home Connect bridge handler from configuration servlet.
248 * @param bridgeHandler bridge handler
250 public void removeBridgeHandler(HomeConnectBridgeHandler bridgeHandler) {
251 bridgeHandlers.remove(bridgeHandler);
254 private void getAppliancesPage(HttpServletRequest request, HttpServletResponse response) throws IOException {
255 WebContext context = new WebContext(request, response, request.getServletContext());
256 context.setVariable("bridgeHandlers", bridgeHandlers);
257 templateEngine.process("appliances", context, response.getWriter());
260 private void processApplianceActions(HttpServletResponse response, String action, String thingId)
262 Optional<HomeConnectBridgeHandler> bridgeHandler = getBridgeHandlerForThing(thingId);
263 Optional<AbstractHomeConnectThingHandler> thingHandler = getThingHandler(thingId);
265 if (bridgeHandler.isPresent() && thingHandler.isPresent()) {
267 response.setContentType(MediaType.APPLICATION_JSON);
268 String haId = thingHandler.get().getThingHaId();
271 case ACTION_SHOW_DETAILS: {
272 String actionResponse = bridgeHandler.get().getApiClient().getRaw(haId,
273 "/api/homeappliances/" + haId);
274 response.getWriter().write(actionResponse != null ? actionResponse : EMPTY_RESPONSE);
277 case ACTION_ALL_PROGRAMS: {
278 String actionResponse = bridgeHandler.get().getApiClient().getRaw(haId,
279 "/api/homeappliances/" + haId + "/programs");
280 response.getWriter().write(actionResponse != null ? actionResponse : EMPTY_RESPONSE);
283 case ACTION_AVAILABLE_PROGRAMS: {
284 String actionResponse = bridgeHandler.get().getApiClient().getRaw(haId,
285 "/api/homeappliances/" + haId + "/programs/available");
286 response.getWriter().write(actionResponse != null ? actionResponse : EMPTY_RESPONSE);
289 case ACTION_SELECTED_PROGRAM: {
290 String actionResponse = bridgeHandler.get().getApiClient().getRaw(haId,
291 "/api/homeappliances/" + haId + "/programs/selected");
292 response.getWriter().write(actionResponse != null ? actionResponse : EMPTY_RESPONSE);
295 case ACTION_ACTIVE_PROGRAM: {
296 String actionResponse = bridgeHandler.get().getApiClient().getRaw(haId,
297 "/api/homeappliances/" + haId + "/programs/active");
298 response.getWriter().write(actionResponse != null ? actionResponse : EMPTY_RESPONSE);
301 case ACTION_OPERATION_STATE: {
302 String actionResponse = bridgeHandler.get().getApiClient().getRaw(haId,
303 "/api/homeappliances/" + haId + "/status/" + EVENT_OPERATION_STATE);
304 response.getWriter().write(actionResponse != null ? actionResponse : EMPTY_RESPONSE);
307 case ACTION_POWER_STATE: {
308 String actionResponse = bridgeHandler.get().getApiClient().getRaw(haId,
309 "/api/homeappliances/" + haId + "/settings/" + EVENT_POWER_STATE);
310 response.getWriter().write(actionResponse != null ? actionResponse : EMPTY_RESPONSE);
313 case ACTION_DOOR_STATE: {
314 String actionResponse = bridgeHandler.get().getApiClient().getRaw(haId,
315 "/api/homeappliances/" + haId + "/status/" + EVENT_DOOR_STATE);
316 response.getWriter().write(actionResponse != null ? actionResponse : EMPTY_RESPONSE);
319 case ACTION_REMOTE_START_ALLOWED: {
320 String actionResponse = bridgeHandler.get().getApiClient().getRaw(haId,
321 "/api/homeappliances/" + haId + "/status/" + EVENT_REMOTE_CONTROL_START_ALLOWED);
322 response.getWriter().write(actionResponse != null ? actionResponse : EMPTY_RESPONSE);
325 case ACTION_REMOTE_CONTROL_ACTIVE: {
326 String actionResponse = bridgeHandler.get().getApiClient().getRaw(haId,
327 "/api/homeappliances/" + haId + "/status/" + EVENT_REMOTE_CONTROL_ACTIVE);
328 response.getWriter().write(actionResponse != null ? actionResponse : EMPTY_RESPONSE);
332 response.sendError(HttpStatus.BAD_REQUEST_400, "Unknown action");
335 } catch (CommunicationException | ApplianceOfflineException | AuthorizationException e) {
336 logger.debug("Could not execute request! thingId={}, action={}, error={}", thingId, action,
338 response.sendError(HttpStatus.INTERNAL_SERVER_ERROR_500, e.getMessage());
341 response.sendError(HttpStatus.BAD_REQUEST_400, "Thing or bridge not found!");
345 private void processRawApplianceActions(HttpServletResponse response, String action, String thingId, String path,
346 String body) throws IOException {
347 Optional<HomeConnectBridgeHandler> bridgeHandler = getBridgeHandlerForThing(thingId);
348 Optional<AbstractHomeConnectThingHandler> thingHandler = getThingHandler(thingId);
350 if (bridgeHandler.isPresent() && thingHandler.isPresent()) {
352 response.setContentType(MediaType.APPLICATION_JSON);
353 String haId = thingHandler.get().getThingHaId();
355 if (ACTION_PUT_RAW.equals(action)) {
356 String actionResponse = bridgeHandler.get().getApiClient().putRaw(haId, path, body);
357 response.getWriter().write(actionResponse);
358 } else if (ACTION_GET_RAW.equals(action)) {
359 String actionResponse = bridgeHandler.get().getApiClient().getRaw(haId, path, true);
360 if (actionResponse == null) {
361 response.getWriter().write("{\"status\": \"No response\"}");
363 response.getWriter().write(actionResponse);
366 response.sendError(HttpStatus.BAD_REQUEST_400, "Unknown action");
368 } catch (CommunicationException | ApplianceOfflineException | AuthorizationException e) {
369 logger.debug("Could not execute request! thingId={}, action={}, error={}", thingId, action,
371 response.sendError(HttpStatus.INTERNAL_SERVER_ERROR_500, e.getMessage());
374 response.sendError(HttpStatus.BAD_REQUEST_400, "Bridge or Thing not found!");
378 private void getBridgesPage(HttpServletRequest request, HttpServletResponse response) throws IOException {
379 WebContext context = new WebContext(request, response, request.getServletContext());
380 context.setVariable("bridgeHandlers", bridgeHandlers);
381 templateEngine.process("bridges", context, response.getWriter());
384 private void postBridgesPage(HttpServletRequest request, HttpServletResponse response) throws IOException {
385 String action = request.getParameter(PARAM_ACTION);
386 String bridgeId = request.getParameter(PARAM_BRIDGE_ID);
387 Optional<HomeConnectBridgeHandler> bridgeHandlerOptional = bridgeHandlers.stream().filter(
388 homeConnectBridgeHandler -> homeConnectBridgeHandler.getThing().getUID().toString().equals(bridgeId))
391 if (bridgeHandlerOptional.isPresent()
392 && (ACTION_AUTHORIZE.equals(action) || ACTION_CLEAR_CREDENTIALS.equals(action))) {
393 HomeConnectBridgeHandler bridgeHandler = bridgeHandlerOptional.get();
394 if (ACTION_AUTHORIZE.equals(action)) {
396 String redirectUri = bridgeHandler.getConfiguration().isSimulator()
397 ? request.getParameter(PARAM_REDIRECT_URI)
399 String authorizationUrl = bridgeHandler.getOAuthClientService().getAuthorizationUrl(redirectUri,
400 null, bridgeHandler.getThing().getUID().getAsString());
401 logger.debug("Generated authorization url: {}", authorizationUrl);
403 response.sendRedirect(authorizationUrl);
404 } catch (OAuthException e) {
405 logger.error("Could not create authorization url!", e);
406 response.sendError(HttpStatus.INTERNAL_SERVER_ERROR_500, "Could not create authorization url!");
409 logger.info("Remove access token for '{}' bridge.", bridgeHandler.getThing().getLabel());
411 bridgeHandler.getOAuthClientService().remove();
412 } catch (OAuthException e) {
413 logger.debug("Could not clear oAuth credentials. error={}", e.getMessage());
415 bridgeHandler.reinitialize();
417 WebContext context = new WebContext(request, response, request.getServletContext());
418 context.setVariable("action",
419 bridgeHandler.getThing().getUID().getAsString() + ACTION_CLEAR_CREDENTIALS);
420 context.setVariable("bridgeHandlers", bridgeHandlers);
421 templateEngine.process("bridges", context, response.getWriter());
424 response.sendError(HttpStatus.BAD_REQUEST_400, "Unknown bridge or action is missing!");
428 private void getRequestLogPage(HttpServletRequest request, HttpServletResponse response) throws IOException {
429 ArrayList<ApiRequest> requests = new ArrayList<>();
430 bridgeHandlers.forEach(homeConnectBridgeHandler -> requests
431 .addAll(homeConnectBridgeHandler.getApiClient().getLatestApiRequests()));
433 WebContext context = new WebContext(request, response, request.getServletContext());
434 context.setVariable("bridgeHandlers", bridgeHandlers);
435 context.setVariable("requests", gson.toJson(requests));
436 templateEngine.process("log-requests", context, response.getWriter());
439 private void getRequestLogExport(HttpServletResponse response, String bridgeId) throws IOException {
440 Optional<HomeConnectBridgeHandler> bridgeHandler = getBridgeHandler(bridgeId);
441 if (bridgeHandler.isPresent()) {
442 response.setContentType(MediaType.APPLICATION_JSON);
443 String fileName = String.format("%s__%s__requests.json", now().format(FILE_EXPORT_DTF),
444 bridgeId.replaceAll("[^a-zA-Z0-9]", "_"));
445 response.setHeader("Content-disposition", "attachment; filename=" + fileName);
447 HashMap<String, Object> responsePayload = new HashMap<>();
448 responsePayload.put("openHAB", OpenHAB.getVersion());
449 responsePayload.put("bundle", FrameworkUtil.getBundle(this.getClass()).getVersion().toString());
450 List<ApiRequest> apiRequestList = bridgeHandler.get().getApiClient().getLatestApiRequests().stream()
451 .peek(apiRequest -> {
452 Map<String, String> headers = apiRequest.getRequest().getHeader();
453 if (headers.containsKey("authorization")) {
454 headers.put("authorization", "*replaced*");
455 } else if (headers.containsKey("Authorization")) {
456 headers.put("Authorization", "*replaced*");
458 }).collect(Collectors.toList());
459 responsePayload.put("requests", apiRequestList);
460 response.getWriter().write(gson.toJson(responsePayload));
462 response.sendError(HttpStatus.BAD_REQUEST_400, "Unknown bridge");
466 private void getEventLogPage(HttpServletRequest request, HttpServletResponse response) throws IOException {
467 WebContext context = new WebContext(request, response, request.getServletContext());
468 context.setVariable("bridgeHandlers", bridgeHandlers);
469 templateEngine.process("log-events", context, response.getWriter());
472 private void getEventLogExport(HttpServletResponse response, String bridgeId) throws IOException {
473 Optional<HomeConnectBridgeHandler> bridgeHandler = getBridgeHandler(bridgeId);
474 if (bridgeHandler.isPresent()) {
475 response.setContentType(MediaType.APPLICATION_JSON);
476 String fileName = String.format("%s__%s__events.json", now().format(FILE_EXPORT_DTF),
477 bridgeId.replaceAll("[^a-zA-Z0-9]", "_"));
478 response.setHeader("Content-disposition", "attachment; filename=" + fileName);
480 HashMap<String, Object> responsePayload = new HashMap<>();
481 responsePayload.put("openHAB", OpenHAB.getVersion());
482 responsePayload.put("bundle", FrameworkUtil.getBundle(this.getClass()).getVersion().toString());
483 responsePayload.put("events", bridgeHandler.get().getEventSourceClient().getLatestEvents());
484 response.getWriter().write(gson.toJson(responsePayload));
486 response.sendError(HttpStatus.BAD_REQUEST_400, "Unknown bridge");
490 private void getBridgeAuthenticationPage(HttpServletRequest request, HttpServletResponse response, String code,
491 String state) throws IOException {
492 // callback handling from authorization server
493 logger.debug("[oAuth] redirect from authorization server (code={}, state={}).", code, state);
495 Optional<HomeConnectBridgeHandler> bridgeHandler = getBridgeHandler(state);
496 if (bridgeHandler.isPresent()) {
498 String redirectUri = bridgeHandler.get().getConfiguration().isSimulator()
499 ? request.getRequestURL().toString()
501 AccessTokenResponse accessTokenResponse = bridgeHandler.get().getOAuthClientService()
502 .getAccessTokenResponseByAuthorizationCode(code, redirectUri);
504 logger.debug("access token response: {}", accessTokenResponse);
507 bridgeHandler.get().reinitialize();
509 WebContext context = new WebContext(request, response, request.getServletContext());
510 context.setVariable("action", bridgeHandler.get().getThing().getUID().getAsString() + ACTION_AUTHORIZE);
511 context.setVariable("bridgeHandlers", bridgeHandlers);
512 templateEngine.process("bridges", context, response.getWriter());
513 } catch (OAuthException | OAuthResponseException e) {
514 logger.error("Could not fetch token!", e);
515 response.sendError(HttpStatus.INTERNAL_SERVER_ERROR_500, "Could not fetch token!");
518 response.sendError(HttpStatus.BAD_REQUEST_400, "Unknown bridge");
522 private boolean pathMatches(String path, String targetPath) {
523 return targetPath.equals(path) || (targetPath + SLASH).equals(path);
526 private Optional<HomeConnectBridgeHandler> getBridgeHandler(String bridgeUid) {
527 for (HomeConnectBridgeHandler handler : bridgeHandlers) {
528 if (handler.getThing().getUID().getAsString().equals(bridgeUid)) {
529 return Optional.of(handler);
532 return Optional.empty();
535 private Optional<AbstractHomeConnectThingHandler> getThingHandler(String thingUid) {
536 for (HomeConnectBridgeHandler handler : bridgeHandlers) {
537 for (AbstractHomeConnectThingHandler thingHandler : handler.getThingHandler()) {
538 if (thingHandler.getThing().getUID().getAsString().equals(thingUid)) {
539 return Optional.of(thingHandler);
543 return Optional.empty();
546 private Optional<HomeConnectBridgeHandler> getBridgeHandlerForThing(String thingUid) {
547 for (HomeConnectBridgeHandler handler : bridgeHandlers) {
548 for (AbstractHomeConnectThingHandler thingHandler : handler.getThingHandler()) {
549 if (thingHandler.getThing().getUID().getAsString().equals(thingUid)) {
550 return Optional.of(handler);
554 return Optional.empty();