]> git.basschouten.com Git - openhab-addons.git/blob
3f4adc217c3eb256ad24e4858358c2c69edd1506
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.omnilink.internal.handler;
14
15 import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
16
17 import java.math.BigInteger;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Optional;
21
22 import javax.measure.quantity.Dimensionless;
23 import javax.measure.quantity.Temperature;
24
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;
45
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;
54
55 /**
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.
59  *
60  * @author Craig Hamilton - Initial contribution
61  * @author Ethan Dye - openHAB3 rewrite
62  */
63 @NonNullByDefault
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;
68
69     private enum ThermostatStatus {
70         HEATING(0, 1),
71         COOLING(1, 2),
72         HUMIDIFYING(2, 3),
73         DEHUMIDIFYING(3, 4);
74
75         private final int bit;
76         private final int modeValue;
77
78         private ThermostatStatus(int bit, int modeValue) {
79             this.bit = bit;
80             this.modeValue = modeValue;
81         }
82     }
83
84     public ThermostatHandler(Thing thing) {
85         super(thing);
86     }
87
88     @Override
89     public void initialize() {
90         super.initialize();
91         final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
92         if (bridgeHandler != null) {
93             updateThermostatProperties(bridgeHandler);
94         } else {
95             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
96                     "Received null bridge while initializing Thermostat!");
97         }
98     }
99
100     private void updateThermostatProperties(OmnilinkBridgeHandler bridgeHandler) {
101         final List<AreaProperties> areas = getAreaProperties();
102         if (areas != null) {
103             for (AreaProperties areaProperties : areas) {
104                 int areaFilter = bitFilterForArea(areaProperties);
105
106                 ObjectPropertyRequest<ThermostatProperties> objectPropertyRequest = ObjectPropertyRequest
107                         .builder(bridgeHandler, ObjectPropertyRequests.THERMOSTAT, thingID, 0).selectNamed()
108                         .areaFilter(areaFilter).build();
109
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);
115                 }
116             }
117         }
118     }
119
120     @Override
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();
126
127         if (command instanceof RefreshType) {
128             retrieveStatus().ifPresentOrElse(this::updateChannels, () -> updateStatus(ThingStatus.OFFLINE,
129                     ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "Received null status update!"));
130             return;
131         }
132
133         if (!(command instanceof DecimalType) && !(command instanceof QuantityType)) {
134             logger.debug("Invalid command: {}, must be DecimalType or QuantityType", command);
135             return;
136         }
137         if (bridgeHandler != null) {
138             temperatureFormat = bridgeHandler.getTemperatureFormat();
139             if (!temperatureFormat.isPresent()) {
140                 logger.warn("Receieved null temperature format!");
141                 return;
142             }
143         } else {
144             logger.warn("Could not connect to Bridge, failed to get temperature format!");
145             return;
146         }
147
148         switch (channelUID.getId()) {
149             case CHANNEL_THERMO_SYSTEM_MODE:
150                 sendOmnilinkCommand(CommandMessage.CMD_THERMO_SET_SYSTEM_MODE, ((DecimalType) command).intValue(),
151                         thingID);
152                 break;
153             case CHANNEL_THERMO_FAN_MODE:
154                 sendOmnilinkCommand(CommandMessage.CMD_THERMO_SET_FAN_MODE, ((DecimalType) command).intValue(),
155                         thingID);
156                 break;
157             case CHANNEL_THERMO_HOLD_STATUS:
158                 sendOmnilinkCommand(CommandMessage.CMD_THERMO_SET_HOLD_MODE, ((DecimalType) command).intValue(),
159                         thingID);
160                 break;
161             case CHANNEL_THERMO_HEAT_SETPOINT:
162                 sendOmnilinkCommand(CommandMessage.CMD_THERMO_SET_HEAT_POINT,
163                         temperatureFormat.get().formatToOmni(((QuantityType<Temperature>) command).floatValue()),
164                         thingID);
165                 break;
166             case CHANNEL_THERMO_COOL_SETPOINT:
167                 sendOmnilinkCommand(CommandMessage.CMD_THERMO_SET_COOL_POINT,
168                         temperatureFormat.get().formatToOmni(((QuantityType<Temperature>) command).floatValue()),
169                         thingID);
170                 break;
171             case CHANNEL_THERMO_HUMIDIFY_SETPOINT:
172                 sendOmnilinkCommand(CommandMessage.CMD_THERMO_SET_HUMDIFY_POINT,
173                         TemperatureFormat.FAHRENHEIT.formatToOmni(((QuantityType<Dimensionless>) command).floatValue()),
174                         thingID);
175                 break;
176             case CHANNEL_THERMO_DEHUMIDIFY_SETPOINT:
177                 sendOmnilinkCommand(CommandMessage.CMD_THERMO_SET_DEHUMIDIFY_POINT,
178                         TemperatureFormat.FAHRENHEIT.formatToOmni(((QuantityType<Dimensionless>) command).floatValue()),
179                         thingID);
180                 break;
181             default:
182                 logger.warn("Unknown channel for Thermostat thing: {}", channelUID);
183         }
184     }
185
186     @Override
187     protected void updateChannels(ExtendedThermostatStatus status) {
188         logger.debug("updateChannels called for Thermostat status: {}", status);
189         final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
190
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);
197
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));
208         } else {
209             updateState(CHANNEL_THERMO_STATUS, new DecimalType(0));
210         }
211
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));
228             } else {
229                 logger.warn("Receieved null temperature format, could not update Thermostat channels!");
230             }
231         } else {
232             logger.warn("Could not connect to Bridge, failed to get temperature format!");
233             return;
234         }
235
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));
243
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()));
249     }
250
251     @Override
252     protected Optional<ExtendedThermostatStatus> retrieveStatus() {
253         try {
254             final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
255             if (bridgeHandler != null) {
256                 ObjectStatus objStatus = bridgeHandler.requestObjectStatus(Message.OBJ_TYPE_THERMO, thingID, thingID,
257                         true);
258                 return Optional.of((ExtendedThermostatStatus) objStatus.getStatuses()[0]);
259             } else {
260                 logger.debug("Received null bridge while updating Thermostat status!");
261                 return Optional.empty();
262             }
263         } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
264             logger.debug("Received exception while refreshing Thermostat status: {}", e.getMessage());
265             return Optional.empty();
266         }
267     }
268 }