2 * Copyright (c) 2010-2021 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.openthermgateway.handler;
15 import java.util.concurrent.ScheduledFuture;
16 import java.util.concurrent.TimeUnit;
18 import javax.measure.Unit;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.openthermgateway.OpenThermGatewayBindingConstants;
23 import org.openhab.binding.openthermgateway.internal.DataItem;
24 import org.openhab.binding.openthermgateway.internal.DataItemGroup;
25 import org.openhab.binding.openthermgateway.internal.GatewayCommand;
26 import org.openhab.binding.openthermgateway.internal.GatewayCommandCode;
27 import org.openhab.binding.openthermgateway.internal.Message;
28 import org.openhab.binding.openthermgateway.internal.OpenThermGatewayCallback;
29 import org.openhab.binding.openthermgateway.internal.OpenThermGatewayConfiguration;
30 import org.openhab.binding.openthermgateway.internal.OpenThermGatewayConnector;
31 import org.openhab.binding.openthermgateway.internal.OpenThermGatewaySocketConnector;
32 import org.openhab.core.library.types.DecimalType;
33 import org.openhab.core.library.types.OnOffType;
34 import org.openhab.core.library.types.QuantityType;
35 import org.openhab.core.library.unit.SIUnits;
36 import org.openhab.core.thing.ChannelUID;
37 import org.openhab.core.thing.Thing;
38 import org.openhab.core.thing.ThingStatus;
39 import org.openhab.core.thing.ThingStatusDetail;
40 import org.openhab.core.thing.binding.BaseThingHandler;
41 import org.openhab.core.types.Command;
42 import org.openhab.core.types.RefreshType;
43 import org.openhab.core.types.State;
44 import org.openhab.core.types.UnDefType;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
49 * The {@link OpenThermGatewayHandler} is responsible for handling commands, which are
50 * sent to one of the channels.
52 * @author Arjen Korevaar - Initial contribution
55 public class OpenThermGatewayHandler extends BaseThingHandler implements OpenThermGatewayCallback {
57 private final Logger logger = LoggerFactory.getLogger(OpenThermGatewayHandler.class);
59 private @Nullable OpenThermGatewayConfiguration config;
60 private @Nullable OpenThermGatewayConnector connector;
61 private @Nullable ScheduledFuture<?> reconnectTask;
63 private boolean connecting = false;
64 private boolean explicitDisconnect = false;
66 public OpenThermGatewayHandler(Thing thing) {
71 public void initialize() {
72 logger.debug("Initializing OpenTherm Gateway handler for uid '{}'", getThing().getUID());
74 updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Initializing");
76 config = getConfigAs(OpenThermGatewayConfiguration.class);
82 public void handleCommand(ChannelUID channelUID, Command command) {
84 OpenThermGatewayConnector conn = connector;
86 logger.debug("Received channel: {}, command: {}", channelUID, command);
88 if (!(command instanceof RefreshType)) {
89 String channel = channelUID.getId();
90 String code = getGatewayCodeFromChannel(channel);
92 GatewayCommand gatewayCommand = null;
94 if (command instanceof OnOffType) {
95 OnOffType onOff = (OnOffType) command;
96 gatewayCommand = GatewayCommand.parse(code, onOff == OnOffType.ON ? "1" : "0");
98 if (command instanceof QuantityType<?>) {
99 QuantityType<?> quantityType = ((QuantityType<?>) command).toUnit(SIUnits.CELSIUS);
101 if (quantityType != null) {
102 double value = quantityType.doubleValue();
103 gatewayCommand = GatewayCommand.parse(code, Double.toString(value));
107 if (gatewayCommand == null) {
108 gatewayCommand = GatewayCommand.parse(code, command.toFullString());
111 if (conn != null && conn.isConnected()) {
112 conn.sendCommand(gatewayCommand);
114 if (code == GatewayCommandCode.ControlSetpoint) {
115 if (gatewayCommand.getMessage().equals("0.0")) {
116 updateState(OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_WATER_SETPOINT,
119 updateState(OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_ENABLED,
120 OnOffType.from(!gatewayCommand.getMessage().equals("0.0")));
129 public void connecting() {
131 updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Connecting");
135 public void connected() {
137 updateStatus(ThingStatus.ONLINE);
141 public void disconnected() {
143 OpenThermGatewayConfiguration conf = config;
147 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Disconnected");
149 // retry connection if disconnect is not explicitly requested
150 if (!explicitDisconnect && conf != null && conf.connectionRetryInterval > 0) {
151 logger.debug("Scheduling to reconnect in {} seconds.", conf.connectionRetryInterval);
152 reconnectTask = scheduler.schedule(this::connect, conf.connectionRetryInterval, TimeUnit.SECONDS);
157 public void receiveMessage(Message message) {
158 if (DataItemGroup.dataItemGroups.containsKey(message.getID())) {
159 DataItem[] dataItems = DataItemGroup.dataItemGroups.get(message.getID());
161 for (DataItem dataItem : dataItems) {
162 String channelId = dataItem.getSubject();
164 if (!OpenThermGatewayBindingConstants.SUPPORTED_CHANNEL_IDS.contains(channelId)
165 || (dataItem.getFilteredCode() != null && dataItem.getFilteredCode() != message.getCode())) {
171 switch (dataItem.getDataType()) {
173 state = OnOffType.from(message.getBit(dataItem.getByteType(), dataItem.getBitPos()));
177 state = new DecimalType(message.getUInt(dataItem.getByteType()));
181 state = new DecimalType(message.getInt(dataItem.getByteType()));
184 float value = message.getFloat();
186 Unit<?> unit = dataItem.getUnit();
187 state = (unit == null) ? new DecimalType(value) : new QuantityType<>(value, unit);
194 logger.debug("Received update for channel '{}': {}", channelId, state);
195 updateState(channelId, state);
202 public void handleRemoval() {
203 logger.debug("Removing OpenTherm Gateway handler");
205 super.handleRemoval();
209 public void dispose() {
212 ScheduledFuture<?> localReconnectTask = reconnectTask;
213 if (localReconnectTask != null) {
214 localReconnectTask.cancel(true);
215 reconnectTask = null;
221 private void connect() {
223 OpenThermGatewayConfiguration conf = config;
225 explicitDisconnect = false;
228 logger.debug("OpenTherm Gateway connector is already connecting ...");
235 logger.debug("Starting OpenTherm Gateway connector");
237 connector = new OpenThermGatewaySocketConnector(this, conf.ipaddress, conf.port);
239 Thread thread = new Thread(connector, "OpenTherm Gateway Binding - socket listener thread");
240 thread.setDaemon(true);
243 logger.debug("OpenTherm Gateway connector started");
247 private void disconnect() {
249 OpenThermGatewayConnector conn = connector;
251 explicitDisconnect = true;
254 if (conn.isConnected()) {
255 logger.debug("Stopping OpenTherm Gateway connector");
263 private @Nullable String getGatewayCodeFromChannel(String channel) throws IllegalArgumentException {
265 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_SETPOINT_TEMPORARY:
266 return GatewayCommandCode.TemperatureTemporary;
267 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_SETPOINT_CONSTANT:
268 return GatewayCommandCode.TemperatureConstant;
269 case OpenThermGatewayBindingConstants.CHANNEL_OUTSIDE_TEMPERATURE:
270 return GatewayCommandCode.TemperatureOutside;
271 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_DHW_SETPOINT:
272 return GatewayCommandCode.SetpointWater;
273 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_WATER_SETPOINT:
274 return GatewayCommandCode.ControlSetpoint;
275 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_ENABLED:
276 return GatewayCommandCode.CentralHeating;
277 case OpenThermGatewayBindingConstants.CHANNEL_SEND_COMMAND:
280 throw new IllegalArgumentException(String.format("Unknown channel %s", channel));