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.openthermgateway.handler;
15 import java.util.concurrent.ScheduledFuture;
16 import java.util.concurrent.TimeUnit;
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.eclipse.jdt.annotation.Nullable;
20 import org.openhab.binding.openthermgateway.internal.ConnectionState;
21 import org.openhab.binding.openthermgateway.internal.DataItemGroup;
22 import org.openhab.binding.openthermgateway.internal.GatewayCommand;
23 import org.openhab.binding.openthermgateway.internal.GatewayCommandCode;
24 import org.openhab.binding.openthermgateway.internal.Message;
25 import org.openhab.binding.openthermgateway.internal.OpenThermGatewayBindingConstants;
26 import org.openhab.binding.openthermgateway.internal.OpenThermGatewayCallback;
27 import org.openhab.binding.openthermgateway.internal.OpenThermGatewayConfiguration;
28 import org.openhab.binding.openthermgateway.internal.OpenThermGatewayConnector;
29 import org.openhab.binding.openthermgateway.internal.OpenThermGatewaySocketConnector;
30 import org.openhab.core.library.types.OnOffType;
31 import org.openhab.core.library.types.QuantityType;
32 import org.openhab.core.library.unit.SIUnits;
33 import org.openhab.core.thing.Bridge;
34 import org.openhab.core.thing.ChannelUID;
35 import org.openhab.core.thing.Thing;
36 import org.openhab.core.thing.ThingStatus;
37 import org.openhab.core.thing.ThingStatusDetail;
38 import org.openhab.core.thing.binding.BaseBridgeHandler;
39 import org.openhab.core.types.Command;
40 import org.openhab.core.types.RefreshType;
41 import org.openhab.core.types.UnDefType;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
46 * The {@link OpenThermGatewayHandler} is responsible for handling commands, which are
47 * sent to one of the channels.
49 * @author Arjen Korevaar - Initial contribution
52 public class OpenThermGatewayHandler extends BaseBridgeHandler implements OpenThermGatewayCallback {
53 private static final String PROPERTY_GATEWAY_ID_NAME = "gatewayId";
54 private static final String PROPERTY_GATEWAY_ID_TAG = "PR: A=";
56 private final Logger logger = LoggerFactory.getLogger(OpenThermGatewayHandler.class);
58 private @Nullable OpenThermGatewayConfiguration configuration;
59 private @Nullable OpenThermGatewayConnector connector;
60 private @Nullable ScheduledFuture<?> reconnectTask;
62 private @Nullable ConnectionState state;
63 private boolean autoReconnect = true;
64 private boolean disposing = false;
66 public OpenThermGatewayHandler(Bridge bridge) {
71 public void initialize() {
72 logger.debug("Initializing OpenThermGateway handler for uid {}", getThing().getUID());
74 configuration = getConfigAs(OpenThermGatewayConfiguration.class);
75 logger.debug("Using configuration: {}", configuration);
78 updateStatus(ThingStatus.UNKNOWN);
83 public void handleCommand(ChannelUID channelUID, Command command) {
84 logger.debug("Received command {} for channel {}", command, channelUID);
86 if (!(command instanceof RefreshType)) {
87 String channel = channelUID.getId();
88 String code = getGatewayCodeFromChannel(channel);
90 GatewayCommand gatewayCommand = null;
92 if (command instanceof OnOffType onOffCommand) {
93 gatewayCommand = GatewayCommand.parse(code, onOffCommand == OnOffType.ON ? "1" : "0");
95 if (command instanceof QuantityType<?> quantityCommand) {
96 QuantityType<?> quantityType = quantityCommand.toUnit(SIUnits.CELSIUS);
98 if (quantityType != null) {
99 double value = quantityType.doubleValue();
100 gatewayCommand = GatewayCommand.parse(code, Double.toString(value));
104 if (gatewayCommand == null) {
105 gatewayCommand = GatewayCommand.parse(code, command.toFullString());
108 sendCommand(gatewayCommand);
110 if (GatewayCommandCode.CONTROLSETPOINT.equals(code)) {
111 if ("0.0".equals(gatewayCommand.getMessage())) {
112 updateState(OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_WATER_SETPOINT,
115 updateState(OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_ENABLED,
116 OnOffType.from(!"0.0".equals(gatewayCommand.getMessage())));
117 } else if (GatewayCommandCode.CONTROLSETPOINT2.equals(code)) {
118 if ("0.0".equals(gatewayCommand.getMessage())) {
119 updateState(OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING2_WATER_SETPOINT,
122 updateState(OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING2_ENABLED,
123 OnOffType.from(!"0.0".equals(gatewayCommand.getMessage())));
128 public void sendCommand(GatewayCommand gatewayCommand) {
130 OpenThermGatewayConnector conn = connector;
132 if (conn != null && conn.isConnected()) {
133 conn.sendCommand(gatewayCommand);
135 logger.debug("Unable to send command {}: connector not connected", gatewayCommand.toFullString());
140 public void receiveMessage(Message message) {
141 scheduler.submit(() -> receiveMessageTask(message));
144 private void receiveMessageTask(Message message) {
145 int msgId = message.getID();
147 if (!DataItemGroup.DATAITEMGROUPS.containsKey(msgId)) {
148 logger.debug("Unsupported message id {}", msgId);
152 for (Thing thing : getThing().getThings()) {
153 BaseDeviceHandler handler = (BaseDeviceHandler) thing.getHandler();
155 if (handler != null) {
156 handler.receiveMessage(message);
162 public void connectionStateChanged(ConnectionState state) {
163 scheduler.submit(() -> connectionStateChangedTask(state));
166 private void connectionStateChangedTask(ConnectionState state) {
167 if (this.state != state) {
172 updateStatus(ThingStatus.ONLINE);
173 cancelAutoReconnect();
177 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
186 public void receiveAcknowledgement(String message) {
187 scheduler.submit(() -> receiveAcknowledgementTask(message));
190 private void receiveAcknowledgementTask(String message) {
191 if (message.startsWith(PROPERTY_GATEWAY_ID_TAG)) {
192 getThing().setProperty(PROPERTY_GATEWAY_ID_NAME,
193 message.substring(PROPERTY_GATEWAY_ID_TAG.length()).strip());
198 public void handleRemoval() {
199 logger.debug("Removing OpenThermGateway handler");
201 super.handleRemoval();
205 public void dispose() {
206 logger.debug("Disposing OpenThermGateway handler");
212 private void connect() {
214 OpenThermGatewayConfiguration config = configuration;
216 if (this.state == ConnectionState.CONNECTING) {
217 logger.debug("OpenThermGateway connector is already connecting");
221 // Make sure everything is cleaned up before creating a new connection
224 if (config != null) {
225 connectionStateChanged(ConnectionState.INITIALIZING);
227 logger.debug("Starting OpenThermGateway connector");
229 autoReconnect = true;
231 OpenThermGatewayConnector conn = connector = new OpenThermGatewaySocketConnector(this, config);
234 logger.debug("OpenThermGateway connector started");
238 private void disconnect() {
239 updateStatus(ThingStatus.OFFLINE);
241 autoReconnect = false;
243 cancelAutoReconnect();
246 OpenThermGatewayConnector conn = connector;
253 private void autoReconnect() {
255 OpenThermGatewayConfiguration config = configuration;
257 if (autoReconnect && config != null && config.connectionRetryInterval > 0) {
258 logger.debug("Scheduling to auto reconnect in {} seconds", config.connectionRetryInterval);
259 reconnectTask = scheduler.schedule(this::connect, config.connectionRetryInterval, TimeUnit.SECONDS);
263 private void cancelAutoReconnect() {
264 ScheduledFuture<?> localReconnectTask = reconnectTask;
266 if (localReconnectTask != null) {
267 if (!localReconnectTask.isDone()) {
268 logger.debug("Cancelling auto reconnect task");
269 localReconnectTask.cancel(true);
272 reconnectTask = null;
276 private @Nullable String getGatewayCodeFromChannel(String channel) throws IllegalArgumentException {
278 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_SETPOINT_TEMPORARY:
279 return GatewayCommandCode.TEMPERATURETEMPORARY;
280 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_SETPOINT_CONSTANT:
281 return GatewayCommandCode.TEMPERATURECONSTANT;
282 case OpenThermGatewayBindingConstants.CHANNEL_OUTSIDE_TEMPERATURE:
283 return GatewayCommandCode.TEMPERATUREOUTSIDE;
284 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_DHW_SETPOINT:
285 return GatewayCommandCode.SETPOINTWATER;
286 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_WATER_SETPOINT:
287 return GatewayCommandCode.CONTROLSETPOINT;
288 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_ENABLED:
289 return GatewayCommandCode.CENTRALHEATING;
290 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING2_WATER_SETPOINT:
291 return GatewayCommandCode.CONTROLSETPOINT2;
292 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING2_ENABLED:
293 return GatewayCommandCode.CENTRALHEATING2;
294 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_VENTILATION_SETPOINT:
295 return GatewayCommandCode.VENTILATIONSETPOINT;
296 case OpenThermGatewayBindingConstants.CHANNEL_SEND_COMMAND:
299 throw new IllegalArgumentException(String.format("Unknown channel %s", channel));