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) {
152 final OnOffType onoff = (OnOffType) command;
153 controller.sendCommand(String.format("%s %s", onoff == 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) {
165 final QuantityType<?> temp = (QuantityType) command;
166 final QuantityType<?> converted = temp.toUnit(controller.getUnit());
167 final String formatted = converted.format("%.1f");
168 controller.sendCommand(String.format("temp %s %s", uid, formatted));
172 if (command instanceof RefreshType) {
173 final String mode = MODE_NUM_TO_STR.get(query(controller, "m"));
175 updateState(MODE, new StringType(mode));
177 } else if (command instanceof StringType) {
178 final String mode = ((StringType) command).toString();
179 controller.sendCommand(String.format("%s %s", mode, uid));
183 if (command instanceof RefreshType) {
184 final String fan = FAN_NUM_TO_STR.get(query(controller, "f"));
186 updateState(FAN_SPEED, new StringType(fan));
188 } else if (command instanceof StringType) {
189 final String fan = ((StringType) command).toString();
190 controller.sendCommand(String.format("fspeed %s %s", uid, fan));
194 if (command instanceof RefreshType) {
195 final String louvre = query(controller, "s");
196 if (louvre != null) {
197 updateState(LOUVRE, new StringType(louvre));
199 } else if (command instanceof StringType) {
200 final String louvre = ((StringType) command).toString();
201 controller.sendCommand(String.format("swing %s %s", uid, louvre));
205 logger.warn("Unknown command '{}' on channel '{}' for unit '{}'", command, channel, uid);
207 updateStatus(ThingStatus.ONLINE);
208 } catch (final IOException ioe) {
209 logger.warn("Failed to handle command '{}' on channel '{}' for unit '{}' due to '{}'", command, channel,
210 uid, ioe.getLocalizedMessage());
211 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ioe.getLocalizedMessage());
216 public void initialize() {
217 cfg = getConfigAs(HVACConfiguration.class);
218 updateStatus(ThingStatus.UNKNOWN);
222 * Update this HVAC unit's properties from the controller.
224 public void refresh() {
225 for (final Channel channel : getThing().getChannels()) {
226 handleCommand(channel.getUID(), RefreshType.REFRESH);
230 private @Nullable String query(final ControllerHandler controller, final String queryChar)
231 throws IOException, CoolMasterClientError {
232 final String uid = getConfigAs(HVACConfiguration.class).uid;
233 final String command = String.format("query %s %s", uid, queryChar);
234 return controller.sendCommand(command);