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.omnilink.internal.handler;
15 import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
17 import java.math.BigInteger;
18 import java.util.List;
20 import java.util.Optional;
22 import javax.measure.quantity.Dimensionless;
23 import javax.measure.quantity.Temperature;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.omnilink.internal.TemperatureFormat;
28 import org.openhab.binding.omnilink.internal.discovery.ObjectPropertyRequest;
29 import org.openhab.binding.omnilink.internal.discovery.ObjectPropertyRequests;
30 import org.openhab.binding.omnilink.internal.exceptions.BridgeOfflineException;
31 import org.openhab.core.library.types.DecimalType;
32 import org.openhab.core.library.types.OpenClosedType;
33 import org.openhab.core.library.types.QuantityType;
34 import org.openhab.core.library.unit.ImperialUnits;
35 import org.openhab.core.library.unit.SIUnits;
36 import org.openhab.core.library.unit.Units;
37 import org.openhab.core.thing.ChannelUID;
38 import org.openhab.core.thing.Thing;
39 import org.openhab.core.thing.ThingStatus;
40 import org.openhab.core.thing.ThingStatusDetail;
41 import org.openhab.core.types.Command;
42 import org.openhab.core.types.RefreshType;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
46 import com.digitaldan.jomnilinkII.Message;
47 import com.digitaldan.jomnilinkII.MessageTypes.CommandMessage;
48 import com.digitaldan.jomnilinkII.MessageTypes.ObjectStatus;
49 import com.digitaldan.jomnilinkII.MessageTypes.properties.AreaProperties;
50 import com.digitaldan.jomnilinkII.MessageTypes.properties.ThermostatProperties;
51 import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedThermostatStatus;
52 import com.digitaldan.jomnilinkII.OmniInvalidResponseException;
53 import com.digitaldan.jomnilinkII.OmniUnknownMessageTypeException;
56 * The {@link ThermostatHandler} defines some methods that are used to
57 * interface with an OmniLink Thermostat. This by extension also defines the
58 * Thermostat thing that openHAB will be able to pick up and interface with.
60 * @author Craig Hamilton - Initial contribution
61 * @author Ethan Dye - openHAB3 rewrite
64 public class ThermostatHandler extends AbstractOmnilinkStatusHandler<ExtendedThermostatStatus> {
65 private final Logger logger = LoggerFactory.getLogger(ThermostatHandler.class);
66 private final int thingID = getThingNumber();
67 public @Nullable String number;
69 private enum ThermostatStatus {
75 private final int bit;
76 private final int modeValue;
78 private ThermostatStatus(int bit, int modeValue) {
80 this.modeValue = modeValue;
84 public ThermostatHandler(Thing thing) {
89 public void initialize() {
91 final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
92 if (bridgeHandler != null) {
93 updateThermostatProperties(bridgeHandler);
95 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
96 "Received null bridge while initializing Thermostat!");
100 private void updateThermostatProperties(OmnilinkBridgeHandler bridgeHandler) {
101 final List<AreaProperties> areas = getAreaProperties();
103 for (AreaProperties areaProperties : areas) {
104 int areaFilter = bitFilterForArea(areaProperties);
106 ObjectPropertyRequest<ThermostatProperties> objectPropertyRequest = ObjectPropertyRequest
107 .builder(bridgeHandler, ObjectPropertyRequests.THERMOSTAT, thingID, 0).selectNamed()
108 .areaFilter(areaFilter).build();
110 for (ThermostatProperties thermostatProperties : objectPropertyRequest) {
111 Map<String, String> properties = editProperties();
112 properties.put(THING_PROPERTIES_NAME, thermostatProperties.getName());
113 properties.put(THING_PROPERTIES_AREA, Integer.toString(areaProperties.getNumber()));
114 updateProperties(properties);
121 @SuppressWarnings("unchecked")
122 public void handleCommand(ChannelUID channelUID, Command command) {
123 logger.debug("handleCommand called for channel: {}, command: {}", channelUID, command);
124 final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
125 Optional<TemperatureFormat> temperatureFormat = Optional.empty();
127 if (command instanceof RefreshType) {
128 retrieveStatus().ifPresentOrElse(this::updateChannels, () -> updateStatus(ThingStatus.OFFLINE,
129 ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "Received null status update!"));
133 if (!(command instanceof DecimalType) && !(command instanceof QuantityType)) {
134 logger.debug("Invalid command: {}, must be DecimalType or QuantityType", command);
137 if (bridgeHandler != null) {
138 temperatureFormat = bridgeHandler.getTemperatureFormat();
139 if (temperatureFormat.isEmpty()) {
140 logger.warn("Receieved null temperature format!");
144 logger.warn("Could not connect to Bridge, failed to get temperature format!");
148 switch (channelUID.getId()) {
149 case CHANNEL_THERMO_SYSTEM_MODE:
150 sendOmnilinkCommand(CommandMessage.CMD_THERMO_SET_SYSTEM_MODE, ((DecimalType) command).intValue(),
153 case CHANNEL_THERMO_FAN_MODE:
154 sendOmnilinkCommand(CommandMessage.CMD_THERMO_SET_FAN_MODE, ((DecimalType) command).intValue(),
157 case CHANNEL_THERMO_HOLD_STATUS:
158 sendOmnilinkCommand(CommandMessage.CMD_THERMO_SET_HOLD_MODE, ((DecimalType) command).intValue(),
161 case CHANNEL_THERMO_HEAT_SETPOINT:
162 sendOmnilinkCommand(CommandMessage.CMD_THERMO_SET_HEAT_POINT,
163 temperatureFormat.get().formatToOmni(((QuantityType<Temperature>) command).floatValue()),
166 case CHANNEL_THERMO_COOL_SETPOINT:
167 sendOmnilinkCommand(CommandMessage.CMD_THERMO_SET_COOL_POINT,
168 temperatureFormat.get().formatToOmni(((QuantityType<Temperature>) command).floatValue()),
171 case CHANNEL_THERMO_HUMIDIFY_SETPOINT:
172 sendOmnilinkCommand(CommandMessage.CMD_THERMO_SET_HUMDIFY_POINT,
173 TemperatureFormat.FAHRENHEIT.formatToOmni(((QuantityType<Dimensionless>) command).floatValue()),
176 case CHANNEL_THERMO_DEHUMIDIFY_SETPOINT:
177 sendOmnilinkCommand(CommandMessage.CMD_THERMO_SET_DEHUMIDIFY_POINT,
178 TemperatureFormat.FAHRENHEIT.formatToOmni(((QuantityType<Dimensionless>) command).floatValue()),
182 logger.warn("Unknown channel for Thermostat thing: {}", channelUID);
187 protected void updateChannels(ExtendedThermostatStatus status) {
188 logger.debug("updateChannels called for Thermostat status: {}", status);
189 final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
191 // Thermostat communication status
192 BigInteger thermostatAlarms = BigInteger.valueOf(status.getStatus());
193 updateState(CHANNEL_THERMO_COMM_FAILURE,
194 thermostatAlarms.testBit(0) ? OpenClosedType.CLOSED : OpenClosedType.OPEN);
195 updateState(CHANNEL_THERMO_FREEZE_ALARM,
196 thermostatAlarms.testBit(1) ? OpenClosedType.CLOSED : OpenClosedType.OPEN);
198 // Thermostat operation status
199 BigInteger thermostatStatus = BigInteger.valueOf(status.getExtendedStatus());
200 if (thermostatStatus.testBit(ThermostatStatus.HEATING.bit)) {
201 updateState(CHANNEL_THERMO_STATUS, new DecimalType(ThermostatStatus.HEATING.modeValue));
202 } else if (thermostatStatus.testBit(ThermostatStatus.COOLING.bit)) {
203 updateState(CHANNEL_THERMO_STATUS, new DecimalType(ThermostatStatus.COOLING.modeValue));
204 } else if (thermostatStatus.testBit(ThermostatStatus.HUMIDIFYING.bit)) {
205 updateState(CHANNEL_THERMO_STATUS, new DecimalType(ThermostatStatus.HUMIDIFYING.modeValue));
206 } else if (thermostatStatus.testBit(ThermostatStatus.DEHUMIDIFYING.bit)) {
207 updateState(CHANNEL_THERMO_STATUS, new DecimalType(ThermostatStatus.DEHUMIDIFYING.modeValue));
209 updateState(CHANNEL_THERMO_STATUS, new DecimalType(0));
212 // Thermostat temperature status
213 if (bridgeHandler != null) {
214 Optional<TemperatureFormat> temperatureFormat = bridgeHandler.getTemperatureFormat();
215 if (temperatureFormat.isPresent()) {
216 updateState(CHANNEL_THERMO_CURRENT_TEMP, new QuantityType<>(
217 temperatureFormat.get().omniToFormat(status.getCurrentTemperature()),
218 temperatureFormat.get().getFormatNumber() == 1 ? ImperialUnits.FAHRENHEIT : SIUnits.CELSIUS));
219 updateState(CHANNEL_THERMO_OUTDOOR_TEMP, new QuantityType<>(
220 temperatureFormat.get().omniToFormat(status.getOutdoorTemperature()),
221 temperatureFormat.get().getFormatNumber() == 1 ? ImperialUnits.FAHRENHEIT : SIUnits.CELSIUS));
222 updateState(CHANNEL_THERMO_COOL_SETPOINT, new QuantityType<>(
223 temperatureFormat.get().omniToFormat(status.getCoolSetpoint()),
224 temperatureFormat.get().getFormatNumber() == 1 ? ImperialUnits.FAHRENHEIT : SIUnits.CELSIUS));
225 updateState(CHANNEL_THERMO_HEAT_SETPOINT, new QuantityType<>(
226 temperatureFormat.get().omniToFormat(status.getHeatSetpoint()),
227 temperatureFormat.get().getFormatNumber() == 1 ? ImperialUnits.FAHRENHEIT : SIUnits.CELSIUS));
229 logger.warn("Receieved null temperature format, could not update Thermostat channels!");
232 logger.warn("Could not connect to Bridge, failed to get temperature format!");
236 // Thermostat humidity status
237 updateState(CHANNEL_THERMO_HUMIDITY, new QuantityType<>(
238 TemperatureFormat.FAHRENHEIT.omniToFormat(status.getCurrentHumidity()), Units.PERCENT));
239 updateState(CHANNEL_THERMO_HUMIDIFY_SETPOINT, new QuantityType<>(
240 TemperatureFormat.FAHRENHEIT.omniToFormat(status.getHumidifySetpoint()), Units.PERCENT));
241 updateState(CHANNEL_THERMO_DEHUMIDIFY_SETPOINT, new QuantityType<>(
242 TemperatureFormat.FAHRENHEIT.omniToFormat(status.getDehumidifySetpoint()), Units.PERCENT));
244 // Thermostat mode, fan, and hold status
245 updateState(CHANNEL_THERMO_SYSTEM_MODE, new DecimalType(status.getSystemMode()));
246 updateState(CHANNEL_THERMO_FAN_MODE, new DecimalType(status.getFanMode()));
247 updateState(CHANNEL_THERMO_HOLD_STATUS,
248 new DecimalType(status.getHoldStatus() > 2 ? 1 : status.getHoldStatus()));
252 protected Optional<ExtendedThermostatStatus> retrieveStatus() {
254 final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
255 if (bridgeHandler != null) {
256 ObjectStatus objStatus = bridgeHandler.requestObjectStatus(Message.OBJ_TYPE_THERMO, thingID, thingID,
258 return Optional.of((ExtendedThermostatStatus) objStatus.getStatuses()[0]);
260 logger.debug("Received null bridge while updating Thermostat status!");
261 return Optional.empty();
263 } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
264 logger.debug("Received exception while refreshing Thermostat status: {}", e.getMessage());
265 return Optional.empty();