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.coolmasternet.internal.handler;
15 import static org.openhab.binding.coolmasternet.internal.CoolMasterNetBindingConstants.*;
17 import java.io.IOException;
18 import java.util.HashMap;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.binding.coolmasternet.internal.ControllerHandler;
24 import org.openhab.binding.coolmasternet.internal.ControllerHandler.CoolMasterClientError;
25 import org.openhab.binding.coolmasternet.internal.config.HVACConfiguration;
26 import org.openhab.core.library.types.OnOffType;
27 import org.openhab.core.library.types.QuantityType;
28 import org.openhab.core.library.types.StringType;
29 import org.openhab.core.thing.Bridge;
30 import org.openhab.core.thing.Channel;
31 import org.openhab.core.thing.ChannelUID;
32 import org.openhab.core.thing.Thing;
33 import org.openhab.core.thing.ThingStatus;
34 import org.openhab.core.thing.ThingStatusDetail;
35 import org.openhab.core.thing.binding.BaseThingHandler;
36 import org.openhab.core.types.Command;
37 import org.openhab.core.types.RefreshType;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
42 * The {@link HVACHandler} is responsible for handling commands for a single
43 * HVAC unit (a single UID on a CoolMasterNet controller.)
45 * @author Angus Gratton - Initial contribution
46 * @author Wouter Born - Fix null pointer exceptions
49 public class HVACHandler extends BaseThingHandler {
52 * The CoolMasterNet protocol's query command returns numbers 0-5 for fan
53 * speed, but the protocol's fan command (and matching binding command) use
54 * single-letter abbreviations.
56 private static final Map<String, String> FAN_NUM_TO_STR;
59 * The CoolMasterNet query command returns numbers 0-5 for operation modes,
60 * but these don't map to any mode you can set on the device, so we use this
63 private static final Map<String, String> MODE_NUM_TO_STR;
66 FAN_NUM_TO_STR = new HashMap<>();
67 FAN_NUM_TO_STR.put("0", "l"); // low
68 FAN_NUM_TO_STR.put("1", "m"); // medium
69 FAN_NUM_TO_STR.put("2", "h"); // high
70 FAN_NUM_TO_STR.put("3", "a"); // auto
71 FAN_NUM_TO_STR.put("4", "t"); // top
73 MODE_NUM_TO_STR = new HashMap<>();
74 MODE_NUM_TO_STR.put("0", "cool");
75 MODE_NUM_TO_STR.put("1", "heat");
76 MODE_NUM_TO_STR.put("2", "auto");
77 MODE_NUM_TO_STR.put("3", "dry");
78 // 4=='haux' but this mode doesn't have an equivalent command to set it
79 MODE_NUM_TO_STR.put("4", "heat");
80 MODE_NUM_TO_STR.put("5", "fan");
83 private HVACConfiguration cfg = new HVACConfiguration();
84 private final Logger logger = LoggerFactory.getLogger(HVACHandler.class);
86 public HVACHandler(final Thing thing) {
91 * Get the controller handler for this bridge.
94 * This method does not raise any exception, but if null is returned it will
95 * always update the Thing status with the reason.
98 * The returned handler may or may not be connected. This method will not
99 * change the Thing status simply because it is not connected, because a
100 * caller may wish to attempt an operation that would result in connection.
102 * @return the controller handler or null if the controller is unavailable
104 private @Nullable ControllerHandler getControllerHandler() {
105 final Bridge bridge = getBridge();
106 if (bridge == null) {
107 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
108 "CoolMasterNet Controller bridge not configured");
112 final ControllerHandler handler = (ControllerHandler) bridge.getHandler();
114 if (handler == null) {
115 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED,
116 "CoolMasterNet Controller bridge not initialized");
124 public void handleCommand(final ChannelUID channelUID, final Command command) {
125 final ControllerHandler controller = getControllerHandler();
126 if (controller == null) {
130 final String uid = cfg.uid;
131 final String channel = channelUID.getId();
136 if (command instanceof RefreshType) {
137 final String currentTemp = query(controller, "a");
138 if (currentTemp != null) {
139 final Integer temp = Integer.parseInt(currentTemp);
140 final QuantityType<?> value = new QuantityType<>(temp, controller.getUnit());
141 updateState(CURRENT_TEMP, value);
146 if (command instanceof RefreshType) {
147 final String on = query(controller, "o");
149 updateState(ON, "1".equals(on) ? OnOffType.ON : OnOffType.OFF);
151 } else if (command instanceof OnOffType onOffCommand) {
153 .sendCommand(String.format("%s %s", onOffCommand == OnOffType.ON ? "on" : "off", uid));
157 if (command instanceof RefreshType) {
158 final String setTemp = query(controller, "t");
159 if (setTemp != null) {
160 final Integer temp = Integer.parseInt(setTemp);
161 final QuantityType<?> value = new QuantityType<>(temp, controller.getUnit());
162 updateState(SET_TEMP, value);
164 } else if (command instanceof QuantityType quantityCommand) {
165 final QuantityType<?> converted = quantityCommand.toUnit(controller.getUnit());
166 final String formatted = converted.format("%.1f");
167 controller.sendCommand(String.format("temp %s %s", uid, formatted));
171 if (command instanceof RefreshType) {
172 final String mode = MODE_NUM_TO_STR.get(query(controller, "m"));
174 updateState(MODE, new StringType(mode));
176 } else if (command instanceof StringType stringCommand) {
177 final String mode = stringCommand.toString();
178 controller.sendCommand(String.format("%s %s", mode, uid));
182 if (command instanceof RefreshType) {
183 final String fan = FAN_NUM_TO_STR.get(query(controller, "f"));
185 updateState(FAN_SPEED, new StringType(fan));
187 } else if (command instanceof StringType stringCommand) {
188 final String fan = stringCommand.toString();
189 controller.sendCommand(String.format("fspeed %s %s", uid, fan));
193 if (command instanceof RefreshType) {
194 final String louvre = query(controller, "s");
195 if (louvre != null) {
196 updateState(LOUVRE, new StringType(louvre));
198 } else if (command instanceof StringType stringCommand) {
199 final String louvre = stringCommand.toString();
200 controller.sendCommand(String.format("swing %s %s", uid, louvre));
204 logger.warn("Unknown command '{}' on channel '{}' for unit '{}'", command, channel, uid);
206 updateStatus(ThingStatus.ONLINE);
207 } catch (final IOException ioe) {
208 logger.warn("Failed to handle command '{}' on channel '{}' for unit '{}' due to '{}'", command, channel,
209 uid, ioe.getLocalizedMessage());
210 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ioe.getLocalizedMessage());
215 public void initialize() {
216 cfg = getConfigAs(HVACConfiguration.class);
217 updateStatus(ThingStatus.UNKNOWN);
221 * Update this HVAC unit's properties from the controller.
223 public void refresh() {
224 for (final Channel channel : getThing().getChannels()) {
225 handleCommand(channel.getUID(), RefreshType.REFRESH);
229 private @Nullable String query(final ControllerHandler controller, final String queryChar)
230 throws IOException, CoolMasterClientError {
231 final String uid = getConfigAs(HVACConfiguration.class).uid;
232 final String command = String.format("query %s %s", uid, queryChar);
233 return controller.sendCommand(command);