2 * Copyright (c) 2010-2024 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.melcloud.internal.handler;
15 import java.util.Collection;
16 import java.util.Collections;
17 import java.util.List;
18 import java.util.Optional;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
23 import org.openhab.binding.melcloud.internal.api.MelCloudConnection;
24 import org.openhab.binding.melcloud.internal.api.json.Device;
25 import org.openhab.binding.melcloud.internal.api.json.DeviceStatus;
26 import org.openhab.binding.melcloud.internal.api.json.HeatpumpDeviceStatus;
27 import org.openhab.binding.melcloud.internal.config.AccountConfig;
28 import org.openhab.binding.melcloud.internal.discovery.MelCloudDiscoveryService;
29 import org.openhab.binding.melcloud.internal.exceptions.MelCloudCommException;
30 import org.openhab.binding.melcloud.internal.exceptions.MelCloudLoginException;
31 import org.openhab.core.thing.Bridge;
32 import org.openhab.core.thing.ChannelUID;
33 import org.openhab.core.thing.ThingStatus;
34 import org.openhab.core.thing.ThingStatusDetail;
35 import org.openhab.core.thing.ThingUID;
36 import org.openhab.core.thing.binding.BaseBridgeHandler;
37 import org.openhab.core.thing.binding.ThingHandlerService;
38 import org.openhab.core.types.Command;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
43 * {@link MelCloudAccountHandler} is the handler for MELCloud API and connects it
46 * @author Luca Calcaterra - Initial contribution
47 * @author Pauli Anttila - Refactoring
48 * @author Wietse van Buitenen - Return all devices, added heatpump device
50 public class MelCloudAccountHandler extends BaseBridgeHandler {
51 private final Logger logger = LoggerFactory.getLogger(MelCloudAccountHandler.class);
53 private MelCloudConnection connection;
54 private List<Device> devices;
55 private ScheduledFuture<?> connectionCheckTask;
56 private AccountConfig config;
57 private boolean loginCredentialError;
59 public MelCloudAccountHandler(Bridge bridge) {
64 public Collection<Class<? extends ThingHandlerService>> getServices() {
65 return Set.of(MelCloudDiscoveryService.class);
69 public void initialize() {
70 logger.debug("Initializing MELCloud account handler.");
71 config = getConfigAs(AccountConfig.class);
72 connection = new MelCloudConnection();
73 devices = Collections.emptyList();
74 loginCredentialError = false;
75 startConnectionCheck();
79 public void dispose() {
80 logger.debug("Running dispose()");
81 stopConnectionCheck();
83 devices = Collections.emptyList();
88 public void handleCommand(ChannelUID channelUID, Command command) {
91 public ThingUID getID() {
92 return getThing().getUID();
95 public List<Device> getDeviceList() throws MelCloudCommException, MelCloudLoginException {
96 connectIfNotConnected();
97 return connection.fetchDeviceList();
100 private void connect() throws MelCloudCommException, MelCloudLoginException {
101 if (loginCredentialError) {
102 throw new MelCloudLoginException("Connection to MELCloud can't be opened because of wrong credentials");
104 logger.debug("Initializing connection to MELCloud");
105 updateStatus(ThingStatus.OFFLINE);
107 connection.login(config.username, config.password, config.language);
108 devices = connection.fetchDeviceList();
109 updateStatus(ThingStatus.ONLINE);
110 } catch (MelCloudLoginException e) {
111 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
112 loginCredentialError = true;
114 } catch (MelCloudCommException e) {
115 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
120 private synchronized void connectIfNotConnected() throws MelCloudCommException, MelCloudLoginException {
121 if (!isConnected()) {
126 public boolean isConnected() {
127 return connection.isConnected();
130 public DeviceStatus sendDeviceStatus(DeviceStatus deviceStatus)
131 throws MelCloudCommException, MelCloudLoginException {
132 connectIfNotConnected();
134 return connection.sendDeviceStatus(deviceStatus);
135 } catch (MelCloudCommException e) {
136 logger.debug("Sending failed, retry once with relogin");
138 return connection.sendDeviceStatus(deviceStatus);
142 public DeviceStatus fetchDeviceStatus(int deviceId, Optional<Integer> buildingId)
143 throws MelCloudCommException, MelCloudLoginException {
144 connectIfNotConnected();
145 int bid = buildingId.orElse(findBuildingId(deviceId));
148 return connection.fetchDeviceStatus(deviceId, bid);
149 } catch (MelCloudCommException e) {
150 logger.debug("Sending failed, retry once with relogin");
152 return connection.fetchDeviceStatus(deviceId, bid);
156 public HeatpumpDeviceStatus sendHeatpumpDeviceStatus(HeatpumpDeviceStatus heatpumpDeviceStatus)
157 throws MelCloudCommException, MelCloudLoginException {
158 connectIfNotConnected();
160 return connection.sendHeatpumpDeviceStatus(heatpumpDeviceStatus);
161 } catch (MelCloudCommException e) {
162 logger.debug("Sending failed, retry once with relogin");
164 return connection.sendHeatpumpDeviceStatus(heatpumpDeviceStatus);
168 public HeatpumpDeviceStatus fetchHeatpumpDeviceStatus(int deviceId, Optional<Integer> buildingId)
169 throws MelCloudCommException, MelCloudLoginException {
170 connectIfNotConnected();
171 int bid = buildingId.orElse(findBuildingId(deviceId));
174 return connection.fetchHeatpumpDeviceStatus(deviceId, bid);
175 } catch (MelCloudCommException e) {
176 logger.debug("Sending failed, retry once with relogin");
178 return connection.fetchHeatpumpDeviceStatus(deviceId, bid);
182 private int findBuildingId(int deviceId) throws MelCloudCommException {
183 if (devices != null) {
184 return devices.stream().filter(d -> d.getDeviceID() == deviceId).findFirst().orElseThrow(
185 () -> new MelCloudCommException(String.format("Can't find building id for device id %s", deviceId)))
188 throw new MelCloudCommException(String.format("Can't find building id for device id %s", deviceId));
191 private void startConnectionCheck() {
192 if (connectionCheckTask == null || connectionCheckTask.isCancelled()) {
193 logger.debug("Start periodic connection check");
194 Runnable runnable = () -> {
195 logger.debug("Check MELCloud connection");
196 if (connection.isConnected()) {
197 logger.debug("Connection to MELCloud open");
201 } catch (MelCloudLoginException e) {
202 logger.debug("Connection to MELCloud down due to login error, reason: {}.", e.getMessage());
203 } catch (MelCloudCommException e) {
204 logger.debug("Connection to MELCloud down, reason: {}.", e.getMessage());
205 } catch (RuntimeException e) {
206 logger.warn("Unknown error occurred during connection check, reason: {}.", e.getMessage(), e);
210 connectionCheckTask = scheduler.scheduleWithFixedDelay(runnable, 0, 300, TimeUnit.SECONDS);
212 logger.debug("Connection check task already running");
216 private void stopConnectionCheck() {
217 if (connectionCheckTask != null) {
218 logger.debug("Stop periodic connection check");
219 connectionCheckTask.cancel(true);
220 connectionCheckTask = null;