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) {
93 OnOffType onOff = (OnOffType) command;
94 gatewayCommand = GatewayCommand.parse(code, onOff == OnOffType.ON ? "1" : "0");
96 if (command instanceof QuantityType<?>) {
97 QuantityType<?> quantityType = ((QuantityType<?>) command).toUnit(SIUnits.CELSIUS);
99 if (quantityType != null) {
100 double value = quantityType.doubleValue();
101 gatewayCommand = GatewayCommand.parse(code, Double.toString(value));
105 if (gatewayCommand == null) {
106 gatewayCommand = GatewayCommand.parse(code, command.toFullString());
109 sendCommand(gatewayCommand);
111 if (GatewayCommandCode.CONTROLSETPOINT.equals(code)) {
112 if (gatewayCommand.getMessage().equals("0.0")) {
113 updateState(OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_WATER_SETPOINT,
116 updateState(OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_ENABLED,
117 OnOffType.from(!gatewayCommand.getMessage().equals("0.0")));
118 } else if (GatewayCommandCode.CONTROLSETPOINT2.equals(code)) {
119 if (gatewayCommand.getMessage().equals("0.0")) {
120 updateState(OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING2_WATER_SETPOINT,
123 updateState(OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING2_ENABLED,
124 OnOffType.from(!gatewayCommand.getMessage().equals("0.0")));
129 public void sendCommand(GatewayCommand gatewayCommand) {
131 OpenThermGatewayConnector conn = connector;
133 if (conn != null && conn.isConnected()) {
134 conn.sendCommand(gatewayCommand);
136 logger.debug("Unable to send command {}: connector not connected", gatewayCommand.toFullString());
141 public void receiveMessage(Message message) {
142 scheduler.submit(() -> receiveMessageTask(message));
145 private void receiveMessageTask(Message message) {
146 int msgId = message.getID();
148 if (!DataItemGroup.DATAITEMGROUPS.containsKey(msgId)) {
149 logger.debug("Unsupported message id {}", msgId);
153 for (Thing thing : getThing().getThings()) {
154 BaseDeviceHandler handler = (BaseDeviceHandler) thing.getHandler();
156 if (handler != null) {
157 handler.receiveMessage(message);
163 public void connectionStateChanged(ConnectionState state) {
164 scheduler.submit(() -> connectionStateChangedTask(state));
167 private void connectionStateChangedTask(ConnectionState state) {
168 if (this.state != state) {
173 updateStatus(ThingStatus.ONLINE);
174 cancelAutoReconnect();
178 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
187 public void receiveAcknowledgement(String message) {
188 scheduler.submit(() -> receiveAcknowledgementTask(message));
191 private void receiveAcknowledgementTask(String message) {
192 if (message.startsWith(PROPERTY_GATEWAY_ID_TAG)) {
193 getThing().setProperty(PROPERTY_GATEWAY_ID_NAME,
194 message.substring(PROPERTY_GATEWAY_ID_TAG.length()).strip());
199 public void handleRemoval() {
200 logger.debug("Removing OpenThermGateway handler");
202 super.handleRemoval();
206 public void dispose() {
207 logger.debug("Disposing OpenThermGateway handler");
213 private void connect() {
215 OpenThermGatewayConfiguration config = configuration;
217 if (this.state == ConnectionState.CONNECTING) {
218 logger.debug("OpenThermGateway connector is already connecting");
222 // Make sure everything is cleaned up before creating a new connection
225 if (config != null) {
226 connectionStateChanged(ConnectionState.INITIALIZING);
228 logger.debug("Starting OpenThermGateway connector");
230 autoReconnect = true;
232 OpenThermGatewayConnector conn = connector = new OpenThermGatewaySocketConnector(this, config);
235 logger.debug("OpenThermGateway connector started");
239 private void disconnect() {
240 updateStatus(ThingStatus.OFFLINE);
242 autoReconnect = false;
244 cancelAutoReconnect();
247 OpenThermGatewayConnector conn = connector;
254 private void autoReconnect() {
256 OpenThermGatewayConfiguration config = configuration;
258 if (autoReconnect && config != null && config.connectionRetryInterval > 0) {
259 logger.debug("Scheduling to auto reconnect in {} seconds", config.connectionRetryInterval);
260 reconnectTask = scheduler.schedule(this::connect, config.connectionRetryInterval, TimeUnit.SECONDS);
264 private void cancelAutoReconnect() {
265 ScheduledFuture<?> localReconnectTask = reconnectTask;
267 if (localReconnectTask != null) {
268 if (!localReconnectTask.isDone()) {
269 logger.debug("Cancelling auto reconnect task");
270 localReconnectTask.cancel(true);
273 reconnectTask = null;
277 private @Nullable String getGatewayCodeFromChannel(String channel) throws IllegalArgumentException {
279 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_SETPOINT_TEMPORARY:
280 return GatewayCommandCode.TEMPERATURETEMPORARY;
281 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_SETPOINT_CONSTANT:
282 return GatewayCommandCode.TEMPERATURECONSTANT;
283 case OpenThermGatewayBindingConstants.CHANNEL_OUTSIDE_TEMPERATURE:
284 return GatewayCommandCode.TEMPERATUREOUTSIDE;
285 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_DHW_SETPOINT:
286 return GatewayCommandCode.SETPOINTWATER;
287 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_WATER_SETPOINT:
288 return GatewayCommandCode.CONTROLSETPOINT;
289 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_ENABLED:
290 return GatewayCommandCode.CENTRALHEATING;
291 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING2_WATER_SETPOINT:
292 return GatewayCommandCode.CONTROLSETPOINT2;
293 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING2_ENABLED:
294 return GatewayCommandCode.CENTRALHEATING2;
295 case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_VENTILATION_SETPOINT:
296 return GatewayCommandCode.VENTILATIONSETPOINT;
297 case OpenThermGatewayBindingConstants.CHANNEL_SEND_COMMAND:
300 throw new IllegalArgumentException(String.format("Unknown channel %s", channel));