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.melcloud.internal.handler;
15 import java.util.Collection;
16 import java.util.Collections;
17 import java.util.List;
18 import java.util.Optional;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
22 import org.openhab.binding.melcloud.internal.api.MelCloudConnection;
23 import org.openhab.binding.melcloud.internal.api.json.Device;
24 import org.openhab.binding.melcloud.internal.api.json.DeviceStatus;
25 import org.openhab.binding.melcloud.internal.api.json.HeatpumpDeviceStatus;
26 import org.openhab.binding.melcloud.internal.config.AccountConfig;
27 import org.openhab.binding.melcloud.internal.discovery.MelCloudDiscoveryService;
28 import org.openhab.binding.melcloud.internal.exceptions.MelCloudCommException;
29 import org.openhab.binding.melcloud.internal.exceptions.MelCloudLoginException;
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.ThingUID;
35 import org.openhab.core.thing.binding.BaseBridgeHandler;
36 import org.openhab.core.thing.binding.ThingHandlerService;
37 import org.openhab.core.types.Command;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
42 * {@link MelCloudAccountHandler} is the handler for MELCloud API and connects it
45 * @author Luca Calcaterra - Initial contribution
46 * @author Pauli Anttila - Refactoring
47 * @author Wietse van Buitenen - Return all devices, added heatpump device
49 public class MelCloudAccountHandler extends BaseBridgeHandler {
50 private final Logger logger = LoggerFactory.getLogger(MelCloudAccountHandler.class);
52 private MelCloudConnection connection;
53 private List<Device> devices;
54 private ScheduledFuture<?> connectionCheckTask;
55 private AccountConfig config;
56 private boolean loginCredentialError;
58 public MelCloudAccountHandler(Bridge bridge) {
63 public Collection<Class<? extends ThingHandlerService>> getServices() {
64 return Collections.singleton(MelCloudDiscoveryService.class);
68 public void initialize() {
69 logger.debug("Initializing MELCloud account handler.");
70 config = getConfigAs(AccountConfig.class);
71 connection = new MelCloudConnection();
72 devices = Collections.emptyList();
73 loginCredentialError = false;
74 startConnectionCheck();
78 public void dispose() {
79 logger.debug("Running dispose()");
80 stopConnectionCheck();
82 devices = Collections.emptyList();
87 public void handleCommand(ChannelUID channelUID, Command command) {
90 public ThingUID getID() {
91 return getThing().getUID();
94 public List<Device> getDeviceList() throws MelCloudCommException, MelCloudLoginException {
95 connectIfNotConnected();
96 return connection.fetchDeviceList();
99 private void connect() throws MelCloudCommException, MelCloudLoginException {
100 if (loginCredentialError) {
101 throw new MelCloudLoginException("Connection to MELCloud can't be opened because of wrong credentials");
103 logger.debug("Initializing connection to MELCloud");
104 updateStatus(ThingStatus.OFFLINE);
106 connection.login(config.username, config.password, config.language);
107 devices = connection.fetchDeviceList();
108 updateStatus(ThingStatus.ONLINE);
109 } catch (MelCloudLoginException e) {
110 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
111 loginCredentialError = true;
113 } catch (MelCloudCommException e) {
114 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
119 private synchronized void connectIfNotConnected() throws MelCloudCommException, MelCloudLoginException {
120 if (!isConnected()) {
125 public boolean isConnected() {
126 return connection.isConnected();
129 public DeviceStatus sendDeviceStatus(DeviceStatus deviceStatus)
130 throws MelCloudCommException, MelCloudLoginException {
131 connectIfNotConnected();
133 return connection.sendDeviceStatus(deviceStatus);
134 } catch (MelCloudCommException e) {
135 logger.debug("Sending failed, retry once with relogin");
137 return connection.sendDeviceStatus(deviceStatus);
141 public DeviceStatus fetchDeviceStatus(int deviceId, Optional<Integer> buildingId)
142 throws MelCloudCommException, MelCloudLoginException {
143 connectIfNotConnected();
144 int bid = buildingId.orElse(findBuildingId(deviceId));
147 return connection.fetchDeviceStatus(deviceId, bid);
148 } catch (MelCloudCommException e) {
149 logger.debug("Sending failed, retry once with relogin");
151 return connection.fetchDeviceStatus(deviceId, bid);
155 public HeatpumpDeviceStatus sendHeatpumpDeviceStatus(HeatpumpDeviceStatus heatpumpDeviceStatus)
156 throws MelCloudCommException, MelCloudLoginException {
157 connectIfNotConnected();
159 return connection.sendHeatpumpDeviceStatus(heatpumpDeviceStatus);
160 } catch (MelCloudCommException e) {
161 logger.debug("Sending failed, retry once with relogin");
163 return connection.sendHeatpumpDeviceStatus(heatpumpDeviceStatus);
167 public HeatpumpDeviceStatus fetchHeatpumpDeviceStatus(int deviceId, Optional<Integer> buildingId)
168 throws MelCloudCommException, MelCloudLoginException {
169 connectIfNotConnected();
170 int bid = buildingId.orElse(findBuildingId(deviceId));
173 return connection.fetchHeatpumpDeviceStatus(deviceId, bid);
174 } catch (MelCloudCommException e) {
175 logger.debug("Sending failed, retry once with relogin");
177 return connection.fetchHeatpumpDeviceStatus(deviceId, bid);
181 private int findBuildingId(int deviceId) throws MelCloudCommException {
182 if (devices != null) {
183 return devices.stream().filter(d -> d.getDeviceID() == deviceId).findFirst().orElseThrow(
184 () -> new MelCloudCommException(String.format("Can't find building id for device id %s", deviceId)))
187 throw new MelCloudCommException(String.format("Can't find building id for device id %s", deviceId));
190 private void startConnectionCheck() {
191 if (connectionCheckTask == null || connectionCheckTask.isCancelled()) {
192 logger.debug("Start periodic connection check");
193 Runnable runnable = () -> {
194 logger.debug("Check MELCloud connection");
195 if (connection.isConnected()) {
196 logger.debug("Connection to MELCloud open");
200 } catch (MelCloudLoginException e) {
201 logger.debug("Connection to MELCloud down due to login error, reason: {}.", e.getMessage());
202 } catch (MelCloudCommException e) {
203 logger.debug("Connection to MELCloud down, reason: {}.", e.getMessage());
204 } catch (RuntimeException e) {
205 logger.warn("Unknown error occurred during connection check, reason: {}.", e.getMessage(), e);
209 connectionCheckTask = scheduler.scheduleWithFixedDelay(runnable, 0, 60, TimeUnit.SECONDS);
211 logger.debug("Connection check task already running");
215 private void stopConnectionCheck() {
216 if (connectionCheckTask != null) {
217 logger.debug("Stop periodic connection check");
218 connectionCheckTask.cancel(true);
219 connectionCheckTask = null;