]> git.basschouten.com Git - openhab-addons.git/blob
e99ebd3ef0270caaf5b1310d5068397a5e6adfba
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.mqtt.homeassistant.internal.component;
14
15 import java.math.BigDecimal;
16 import java.util.Arrays;
17 import java.util.List;
18 import java.util.function.Predicate;
19
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
23 import org.openhab.binding.mqtt.generic.values.NumberValue;
24 import org.openhab.binding.mqtt.generic.values.OnOffValue;
25 import org.openhab.binding.mqtt.generic.values.TextValue;
26 import org.openhab.binding.mqtt.generic.values.Value;
27 import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel;
28 import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
29 import org.openhab.core.library.types.StringType;
30 import org.openhab.core.types.Command;
31 import org.openhab.core.types.State;
32
33 import com.google.gson.annotations.SerializedName;
34
35 /**
36  * A MQTT climate component, following the https://www.home-assistant.io/components/climate.mqtt/ specification.
37  *
38  * @author David Graeff - Initial contribution
39  * @author Anton Kharuzhy - Implementation
40  */
41 @NonNullByDefault
42 public class Climate extends AbstractComponent<Climate.ChannelConfiguration> {
43     public static final String ACTION_CH_ID = "action";
44     public static final String AUX_CH_ID = "aux";
45     public static final String AWAY_MODE_CH_ID = "awayMode";
46     public static final String CURRENT_TEMPERATURE_CH_ID = "currentTemperature";
47     public static final String FAN_MODE_CH_ID = "fanMode";
48     public static final String HOLD_CH_ID = "hold";
49     public static final String MODE_CH_ID = "mode";
50     public static final String SWING_CH_ID = "swing";
51     public static final String TEMPERATURE_CH_ID = "temperature";
52     public static final String TEMPERATURE_HIGH_CH_ID = "temperatureHigh";
53     public static final String TEMPERATURE_LOW_CH_ID = "temperatureLow";
54     public static final String POWER_CH_ID = "power";
55
56     private static final String CELSIUM = "C";
57     private static final String FAHRENHEIT = "F";
58     private static final float DEFAULT_CELSIUM_PRECISION = 0.1f;
59     private static final float DEFAULT_FAHRENHEIT_PRECISION = 1f;
60
61     private static final String ACTION_OFF = "off";
62     private static final State ACTION_OFF_STATE = new StringType(ACTION_OFF);
63     private static final List<String> ACTION_MODES = List.of(ACTION_OFF, "heating", "cooling", "drying", "idle", "fan");
64
65     /**
66      * Configuration class for MQTT component
67      */
68     static class ChannelConfiguration extends AbstractChannelConfiguration {
69         ChannelConfiguration() {
70             super("MQTT HVAC");
71         }
72
73         @SerializedName("action_template")
74         protected @Nullable String actionTemplate;
75         @SerializedName("action_topic")
76         protected @Nullable String actionTopic;
77
78         @SerializedName("aux_command_topic")
79         protected @Nullable String auxCommandTopic;
80         @SerializedName("aux_state_template")
81         protected @Nullable String auxStateTemplate;
82         @SerializedName("aux_state_topic")
83         protected @Nullable String auxStateTopic;
84
85         @SerializedName("away_mode_command_topic")
86         protected @Nullable String awayModeCommandTopic;
87         @SerializedName("away_mode_state_template")
88         protected @Nullable String awayModeStateTemplate;
89         @SerializedName("away_mode_state_topic")
90         protected @Nullable String awayModeStateTopic;
91
92         @SerializedName("current_temperature_template")
93         protected @Nullable String currentTemperatureTemplate;
94         @SerializedName("current_temperature_topic")
95         protected @Nullable String currentTemperatureTopic;
96
97         @SerializedName("fan_mode_command_template")
98         protected @Nullable String fanModeCommandTemplate;
99         @SerializedName("fan_mode_command_topic")
100         protected @Nullable String fanModeCommandTopic;
101         @SerializedName("fan_mode_state_template")
102         protected @Nullable String fanModeStateTemplate;
103         @SerializedName("fan_mode_state_topic")
104         protected @Nullable String fanModeStateTopic;
105         @SerializedName("fan_modes")
106         protected List<String> fanModes = Arrays.asList("auto", "low", "medium", "high");
107
108         @SerializedName("hold_command_template")
109         protected @Nullable String holdCommandTemplate;
110         @SerializedName("hold_command_topic")
111         protected @Nullable String holdCommandTopic;
112         @SerializedName("hold_state_template")
113         protected @Nullable String holdStateTemplate;
114         @SerializedName("hold_state_topic")
115         protected @Nullable String holdStateTopic;
116         @SerializedName("hold_modes")
117         protected @Nullable List<String> holdModes; // Are there default modes? Now the channel will be ignored without
118                                                     // hold modes.
119
120         @SerializedName("json_attributes_template")
121         protected @Nullable String jsonAttributesTemplate; // Attributes are not supported yet
122         @SerializedName("json_attributes_topic")
123         protected @Nullable String jsonAttributesTopic;
124
125         @SerializedName("mode_command_template")
126         protected @Nullable String modeCommandTemplate;
127         @SerializedName("mode_command_topic")
128         protected @Nullable String modeCommandTopic;
129         @SerializedName("mode_state_template")
130         protected @Nullable String modeStateTemplate;
131         @SerializedName("mode_state_topic")
132         protected @Nullable String modeStateTopic;
133         protected List<String> modes = Arrays.asList("auto", "off", "cool", "heat", "dry", "fan_only");
134
135         @SerializedName("swing_command_template")
136         protected @Nullable String swingCommandTemplate;
137         @SerializedName("swing_command_topic")
138         protected @Nullable String swingCommandTopic;
139         @SerializedName("swing_state_template")
140         protected @Nullable String swingStateTemplate;
141         @SerializedName("swing_state_topic")
142         protected @Nullable String swingStateTopic;
143         @SerializedName("swing_modes")
144         protected List<String> swingModes = Arrays.asList("on", "off");
145
146         @SerializedName("temperature_command_template")
147         protected @Nullable String temperatureCommandTemplate;
148         @SerializedName("temperature_command_topic")
149         protected @Nullable String temperatureCommandTopic;
150         @SerializedName("temperature_state_template")
151         protected @Nullable String temperatureStateTemplate;
152         @SerializedName("temperature_state_topic")
153         protected @Nullable String temperatureStateTopic;
154
155         @SerializedName("temperature_high_command_template")
156         protected @Nullable String temperatureHighCommandTemplate;
157         @SerializedName("temperature_high_command_topic")
158         protected @Nullable String temperatureHighCommandTopic;
159         @SerializedName("temperature_high_state_template")
160         protected @Nullable String temperatureHighStateTemplate;
161         @SerializedName("temperature_high_state_topic")
162         protected @Nullable String temperatureHighStateTopic;
163
164         @SerializedName("temperature_low_command_template")
165         protected @Nullable String temperatureLowCommandTemplate;
166         @SerializedName("temperature_low_command_topic")
167         protected @Nullable String temperatureLowCommandTopic;
168         @SerializedName("temperature_low_state_template")
169         protected @Nullable String temperatureLowStateTemplate;
170         @SerializedName("temperature_low_state_topic")
171         protected @Nullable String temperatureLowStateTopic;
172
173         @SerializedName("power_command_topic")
174         protected @Nullable String powerCommandTopic;
175
176         protected Integer initial = 21;
177         @SerializedName("max_temp")
178         protected @Nullable Float maxTemp;
179         @SerializedName("min_temp")
180         protected @Nullable Float minTemp;
181         @SerializedName("temperature_unit")
182         protected String temperatureUnit = CELSIUM; // System unit by default
183         @SerializedName("temp_step")
184         protected Float tempStep = 1f;
185         protected @Nullable Float precision;
186         @SerializedName("send_if_off")
187         protected Boolean sendIfOff = true;
188     }
189
190     public Climate(ComponentFactory.ComponentConfiguration componentConfiguration) {
191         super(componentConfiguration, ChannelConfiguration.class);
192
193         BigDecimal minTemp = channelConfiguration.minTemp != null ? BigDecimal.valueOf(channelConfiguration.minTemp)
194                 : null;
195         BigDecimal maxTemp = channelConfiguration.maxTemp != null ? BigDecimal.valueOf(channelConfiguration.maxTemp)
196                 : null;
197         float precision = channelConfiguration.precision != null ? channelConfiguration.precision
198                 : (FAHRENHEIT.equals(channelConfiguration.temperatureUnit) ? DEFAULT_FAHRENHEIT_PRECISION
199                         : DEFAULT_CELSIUM_PRECISION);
200         final ChannelStateUpdateListener updateListener = componentConfiguration.getUpdateListener();
201
202         ComponentChannel actionChannel = buildOptionalChannel(ACTION_CH_ID,
203                 new TextValue(ACTION_MODES.toArray(new String[0])), updateListener, null, null,
204                 channelConfiguration.actionTemplate, channelConfiguration.actionTopic, null);
205
206         final Predicate<Command> commandFilter = channelConfiguration.sendIfOff ? null
207                 : getCommandFilter(actionChannel);
208
209         buildOptionalChannel(AUX_CH_ID, new OnOffValue(), updateListener, null, channelConfiguration.auxCommandTopic,
210                 channelConfiguration.auxStateTemplate, channelConfiguration.auxStateTopic, commandFilter);
211
212         buildOptionalChannel(AWAY_MODE_CH_ID, new OnOffValue(), updateListener, null,
213                 channelConfiguration.awayModeCommandTopic, channelConfiguration.awayModeStateTemplate,
214                 channelConfiguration.awayModeStateTopic, commandFilter);
215
216         buildOptionalChannel(CURRENT_TEMPERATURE_CH_ID,
217                 new NumberValue(minTemp, maxTemp, BigDecimal.valueOf(precision), channelConfiguration.temperatureUnit),
218                 updateListener, null, null, channelConfiguration.currentTemperatureTemplate,
219                 channelConfiguration.currentTemperatureTopic, commandFilter);
220
221         buildOptionalChannel(FAN_MODE_CH_ID, new TextValue(channelConfiguration.fanModes.toArray(new String[0])),
222                 updateListener, channelConfiguration.fanModeCommandTemplate, channelConfiguration.fanModeCommandTopic,
223                 channelConfiguration.fanModeStateTemplate, channelConfiguration.fanModeStateTopic, commandFilter);
224
225         if (channelConfiguration.holdModes != null && !channelConfiguration.holdModes.isEmpty()) {
226             buildOptionalChannel(HOLD_CH_ID, new TextValue(channelConfiguration.holdModes.toArray(new String[0])),
227                     updateListener, channelConfiguration.holdCommandTemplate, channelConfiguration.holdCommandTopic,
228                     channelConfiguration.holdStateTemplate, channelConfiguration.holdStateTopic, commandFilter);
229         }
230
231         buildOptionalChannel(MODE_CH_ID, new TextValue(channelConfiguration.modes.toArray(new String[0])),
232                 updateListener, channelConfiguration.modeCommandTemplate, channelConfiguration.modeCommandTopic,
233                 channelConfiguration.modeStateTemplate, channelConfiguration.modeStateTopic, commandFilter);
234
235         buildOptionalChannel(SWING_CH_ID, new TextValue(channelConfiguration.swingModes.toArray(new String[0])),
236                 updateListener, channelConfiguration.swingCommandTemplate, channelConfiguration.swingCommandTopic,
237                 channelConfiguration.swingStateTemplate, channelConfiguration.swingStateTopic, commandFilter);
238
239         buildOptionalChannel(TEMPERATURE_CH_ID,
240                 new NumberValue(minTemp, maxTemp, BigDecimal.valueOf(channelConfiguration.tempStep),
241                         channelConfiguration.temperatureUnit),
242                 updateListener, channelConfiguration.temperatureCommandTemplate,
243                 channelConfiguration.temperatureCommandTopic, channelConfiguration.temperatureStateTemplate,
244                 channelConfiguration.temperatureStateTopic, commandFilter);
245
246         buildOptionalChannel(TEMPERATURE_HIGH_CH_ID,
247                 new NumberValue(minTemp, maxTemp, BigDecimal.valueOf(channelConfiguration.tempStep),
248                         channelConfiguration.temperatureUnit),
249                 updateListener, channelConfiguration.temperatureHighCommandTemplate,
250                 channelConfiguration.temperatureHighCommandTopic, channelConfiguration.temperatureHighStateTemplate,
251                 channelConfiguration.temperatureHighStateTopic, commandFilter);
252
253         buildOptionalChannel(TEMPERATURE_LOW_CH_ID,
254                 new NumberValue(minTemp, maxTemp, BigDecimal.valueOf(channelConfiguration.tempStep),
255                         channelConfiguration.temperatureUnit),
256                 updateListener, channelConfiguration.temperatureLowCommandTemplate,
257                 channelConfiguration.temperatureLowCommandTopic, channelConfiguration.temperatureLowStateTemplate,
258                 channelConfiguration.temperatureLowStateTopic, commandFilter);
259
260         buildOptionalChannel(POWER_CH_ID, new OnOffValue(), updateListener, null,
261                 channelConfiguration.powerCommandTopic, null, null, null);
262     }
263
264     @Nullable
265     private ComponentChannel buildOptionalChannel(String channelId, Value valueState,
266             ChannelStateUpdateListener channelStateUpdateListener, @Nullable String commandTemplate,
267             @Nullable String commandTopic, @Nullable String stateTemplate, @Nullable String stateTopic,
268             @Nullable Predicate<Command> commandFilter) {
269         if ((commandTopic != null && !commandTopic.isBlank()) || (stateTopic != null && !stateTopic.isBlank())) {
270             return buildChannel(channelId, valueState, channelConfiguration.getName(), channelStateUpdateListener)
271                     .stateTopic(stateTopic, stateTemplate, channelConfiguration.getValueTemplate())
272                     .commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos(),
273                             commandTemplate)
274                     .commandFilter(commandFilter).build();
275         }
276         return null;
277     }
278
279     private @Nullable Predicate<Command> getCommandFilter(@Nullable ComponentChannel actionChannel) {
280         if (actionChannel == null) {
281             return null;
282         }
283         final var val = actionChannel.getState().getCache();
284         return command -> !ACTION_OFF_STATE.equals(val.getChannelState());
285     }
286 }