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.ecotouch.internal;
15 import static org.openhab.core.library.unit.SIUnits.*;
16 import static org.openhab.core.library.unit.Units.*;
18 import java.io.IOException;
19 import java.math.BigDecimal;
20 import java.math.RoundingMode;
21 import java.util.HashSet;
22 import java.util.Iterator;
23 import java.util.List;
26 import java.util.concurrent.ScheduledFuture;
27 import java.util.concurrent.TimeUnit;
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.openhab.core.library.types.DecimalType;
32 import org.openhab.core.library.types.QuantityType;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.ThingStatus;
36 import org.openhab.core.thing.ThingStatusDetail;
37 import org.openhab.core.thing.binding.BaseThingHandler;
38 import org.openhab.core.types.Command;
39 import org.openhab.core.types.RefreshType;
40 import org.openhab.core.types.State;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
45 * The {@link EcoTouchHandler} is responsible for handling commands, which are
46 * sent to one of the channels.
48 * @author Sebastian Held - Initial contribution
51 public class EcoTouchHandler extends BaseThingHandler {
53 private @Nullable EcoTouchConnector connector = null;
55 private final Logger logger = LoggerFactory.getLogger(EcoTouchHandler.class);
57 private @Nullable EcoTouchConfiguration config = null;
59 private @Nullable ScheduledFuture<?> refreshJob = null;
61 public EcoTouchHandler(Thing thing) {
65 private void updateChannel(String tag, String value_str) {
67 List<EcoTouchTags> ecoTouchTags = EcoTouchTags.fromTag(tag);
68 for (EcoTouchTags ecoTouchTag : ecoTouchTags) {
69 String channel = ecoTouchTag.getCommand();
70 BigDecimal value = ecoTouchTag.decodeValue(value_str);
71 if (ecoTouchTag == EcoTouchTags.TYPE_ADAPT_HEATING) {
72 // this type needs special treatment
73 // the following reads: value = value / 2 - 2
74 value = value.divide(new BigDecimal(2), 1, RoundingMode.UNNECESSARY).subtract(new BigDecimal(2));
75 QuantityType<?> quantity = new QuantityType<javax.measure.quantity.Temperature>(value, CELSIUS);
76 updateState(channel, quantity);
78 if (ecoTouchTag.getUnit() != ONE) {
79 // this is a quantity type
80 QuantityType<?> quantity = new QuantityType<>(value, ecoTouchTag.getUnit());
81 updateState(channel, quantity);
83 DecimalType number = new DecimalType(value);
84 updateState(channel, number);
88 } catch (Exception e) {
93 public void handleCommand(ChannelUID channelUID, Command command) {
94 var localConnector = connector;
95 if (localConnector == null) {
96 // the binding was not initialized, yet
99 if (command instanceof RefreshType) {
100 // request a refresh of a channel
102 EcoTouchTags tag = EcoTouchTags.fromString(channelUID.getId());
104 String valueStr = localConnector.getValue(tag.getTagName());
105 updateChannel(tag.getTagName(), valueStr);
106 updateStatus(ThingStatus.ONLINE);
108 } catch (Exception e) {
111 // send command to heat pump
113 EcoTouchTags ecoTouchTag = EcoTouchTags.fromString(channelUID.getId());
114 if (ecoTouchTag == null) {
115 logger.warn("ID: {} unknown", channelUID.getId());
118 if (ecoTouchTag == EcoTouchTags.TYPE_ADAPT_HEATING) {
119 // this type needs special treatment
120 QuantityType<?> value = (QuantityType<?>) command;
121 int raw = Math.round(value.floatValue() * 2 + 4);
122 localConnector.setValue(ecoTouchTag.getTagName(), raw);
124 if (ecoTouchTag.getUnit() != ONE) {
125 if (command instanceof QuantityType) {
126 // convert from user unit to heat pump unit
127 QuantityType<?> value = (QuantityType<?>) command;
128 QuantityType<?> rawUnit = value.toUnit(ecoTouchTag.getUnit());
129 if (rawUnit != null) {
130 int raw = (int) (rawUnit.doubleValue() * ecoTouchTag.getDivisor());
131 localConnector.setValue(ecoTouchTag.getTagName(), raw);
134 logger.debug("handleCommand: requires a QuantityType");
137 State state = (State) command;
138 DecimalType decimalType = state.as(DecimalType.class);
139 if (decimalType != null) {
140 BigDecimal decimal = decimalType.toBigDecimal();
141 decimal = decimal.multiply(new BigDecimal(ecoTouchTag.getDivisor()));
142 int raw = decimal.intValue();
143 localConnector.setValue(ecoTouchTag.getTagName(), raw);
145 logger.debug("cannot convert {} to a DecimalType", state);
149 } catch (Exception e) {
150 logger.debug("handleCommand: {}", e.toString());
156 public void initialize() {
157 config = getConfigAs(EcoTouchConfiguration.class);
159 var localConfig = config;
160 if (localConfig == null) {
161 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
165 connector = new EcoTouchConnector(localConfig.ip, localConfig.username, localConfig.password);
167 scheduler.execute(() -> {
169 // try to get a single value
170 var localConnector = connector;
171 if (localConnector == null) {
172 updateStatus(ThingStatus.OFFLINE);
175 localConnector.getValue("A1");
176 } catch (IOException io) {
177 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, io.toString());
179 } catch (Exception e) {
180 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.toString());
184 updateStatus(ThingStatus.ONLINE);
187 // start refresh handler
188 startAutomaticRefresh();
191 private void startAutomaticRefresh() {
192 var localRefreshJob = refreshJob;
193 if (localRefreshJob == null || localRefreshJob.isCancelled()) {
194 Runnable runnable = () -> {
196 Set<String> tags = new HashSet<String>();
197 for (EcoTouchTags ecoTouchTag : EcoTouchTags.values()) {
198 String channel = ecoTouchTag.getCommand();
199 boolean linked = isLinked(channel);
201 tags.add(ecoTouchTag.getTagName());
204 var localConnector = connector;
205 if (localConnector != null) {
206 Map<String, String> result = localConnector.getValues(tags);
208 Iterator<Map.Entry<String, String>> it = result.entrySet().iterator();
209 while (it.hasNext()) {
210 Map.Entry<String, String> pair = it.next();
211 updateChannel(pair.getKey(), pair.getValue());
214 } catch (IOException io) {
215 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, io.toString());
216 } catch (Exception e) {
217 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.toString());
219 // during thing creation, the following error is thrown:
220 // java.lang.NoSuchMethodError: 'org.openhab.binding.ecotouch.internal.EcoTouchTags[]
221 // org.openhab.binding.ecotouch.internal.EcoTouchTags.values()'
222 // not sure why... lets ignore it for now
223 updateStatus(ThingStatus.OFFLINE);
227 var localConfig = config;
228 if (localConfig != null) {
229 refreshJob = scheduler.scheduleWithFixedDelay(runnable, 10, localConfig.refresh, TimeUnit.SECONDS);
235 public void dispose() {
236 var localRefreshJob = refreshJob;
237 if (localRefreshJob != null && !localRefreshJob.isCancelled()) {
238 localRefreshJob.cancel(true);
239 localRefreshJob = null;
241 var localConnector = connector;
242 if (localConnector != null) {
243 localConnector.logout();