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 (GatewayCommandCode.ControlSetpoint.equals(code)) {
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")));
121 } else if (GatewayCommandCode.ControlSetpoint2.equals(code)) {
122 if (gatewayCommand.getMessage().equals("0.0")) {
123 updateState(OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING2_WATER_SETPOINT,
126 updateState(OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING2_ENABLED,
127 OnOffType.from(!gatewayCommand.getMessage().equals("0.0")));
136 public void connecting() {
138 updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Connecting");
142 public void connected() {
144 updateStatus(ThingStatus.ONLINE);
148 public void disconnected() {
150 OpenThermGatewayConfiguration conf = config;
154 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Disconnected");
156 // retry connection if disconnect is not explicitly requested
157 if (!explicitDisconnect && conf != null && conf.connectionRetryInterval > 0) {
158 logger.debug("Scheduling to reconnect in {} seconds.", conf.connectionRetryInterval);
159 reconnectTask = scheduler.schedule(this::connect, conf.connectionRetryInterval, TimeUnit.SECONDS);
164 public void receiveMessage(Message message) {
165 if (DataItemGroup.dataItemGroups.containsKey(message.getID())) {
166 DataItem[] dataItems = DataItemGroup.dataItemGroups.get(message.getID());
168 for (DataItem dataItem : dataItems) {
169 String channelId = dataItem.getSubject();
171 if (!OpenThermGatewayBindingConstants.SUPPORTED_CHANNEL_IDS.contains(channelId)
172 || (dataItem.getFilteredCode() != null && dataItem.getFilteredCode() != message.getCode())) {
178 switch (dataItem.getDataType()) {
180 state = OnOffType.from(message.getBit(dataItem.getByteType(), dataItem.getBitPos()));
184 state = new DecimalType(message.getUInt(dataItem.getByteType()));
188 state = new DecimalType(message.getInt(dataItem.getByteType()));
191 float value = message.getFloat();
193 Unit<?> unit = dataItem.getUnit();
194 state = (unit == null) ? new DecimalType(value) : new QuantityType<>(value, unit);
201 logger.debug("Received update for channel '{}': {}", channelId, state);
202 updateState(channelId, state);
209 public void handleRemoval() {
210 logger.debug("Removing OpenTherm Gateway handler");
212 super.handleRemoval();
216 public void dispose() {
219 ScheduledFuture<?> localReconnectTask = reconnectTask;
220 if (localReconnectTask != null) {
221 localReconnectTask.cancel(true);
222 reconnectTask = null;
228 private void connect() {
230 OpenThermGatewayConfiguration conf = config;
232 explicitDisconnect = false;
235 logger.debug("OpenTherm Gateway connector is already connecting ...");
242 logger.debug("Starting OpenTherm Gateway connector");
244 connector = new OpenThermGatewaySocketConnector(this, conf.ipaddress, conf.port);
246 Thread thread = new Thread(connector, "OpenTherm Gateway Binding - socket listener thread");
247 thread.setDaemon(true);
250 logger.debug("OpenTherm Gateway connector started");
254 private void disconnect() {
256 OpenThermGatewayConnector conn = connector;
258 explicitDisconnect = true;
261 if (conn.isConnected()) {
262 logger.debug("Stopping OpenTherm Gateway connector");
270 private @Nullable String getGatewayCodeFromChannel(String channel) throws IllegalArgumentException {
272 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_SETPOINT_TEMPORARY:
273 return GatewayCommandCode.TemperatureTemporary;
274 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_SETPOINT_CONSTANT:
275 return GatewayCommandCode.TemperatureConstant;
276 case OpenThermGatewayBindingConstants.CHANNEL_OUTSIDE_TEMPERATURE:
277 return GatewayCommandCode.TemperatureOutside;
278 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_DHW_SETPOINT:
279 return GatewayCommandCode.SetpointWater;
280 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_WATER_SETPOINT:
281 return GatewayCommandCode.ControlSetpoint;
282 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_ENABLED:
283 return GatewayCommandCode.CentralHeating;
284 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING2_WATER_SETPOINT:
285 return GatewayCommandCode.ControlSetpoint2;
286 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING2_ENABLED:
287 return GatewayCommandCode.CentralHeating2;
288 case OpenThermGatewayBindingConstants.CHANNEL_SEND_COMMAND:
291 throw new IllegalArgumentException(String.format("Unknown channel %s", channel));