]> git.basschouten.com Git - openhab-addons.git/blob
c030d8a27e081f9230c7e956f44d1a2ace184043
[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 /**
34  * A MQTT climate component, following the https://www.home-assistant.io/components/climate.mqtt/ specification.
35  *
36  * @author David Graeff - Initial contribution
37  * @author Anton Kharuzhy - Implementation
38  */
39 @NonNullByDefault
40 public class Climate extends AbstractComponent<Climate.ChannelConfiguration> {
41     public static final String ACTION_CH_ID = "action";
42     public static final String AUX_CH_ID = "aux";
43     public static final String AWAY_MODE_CH_ID = "awayMode";
44     public static final String CURRENT_TEMPERATURE_CH_ID = "currentTemperature";
45     public static final String FAN_MODE_CH_ID = "fanMode";
46     public static final String HOLD_CH_ID = "hold";
47     public static final String MODE_CH_ID = "mode";
48     public static final String SWING_CH_ID = "swing";
49     public static final String TEMPERATURE_CH_ID = "temperature";
50     public static final String TEMPERATURE_HIGH_CH_ID = "temperatureHigh";
51     public static final String TEMPERATURE_LOW_CH_ID = "temperatureLow";
52     public static final String POWER_CH_ID = "power";
53
54     private static final String CELSIUM = "C";
55     private static final String FAHRENHEIT = "F";
56     private static final float DEFAULT_CELSIUM_PRECISION = 0.1f;
57     private static final float DEFAULT_FAHRENHEIT_PRECISION = 1f;
58
59     private static final String ACTION_OFF = "off";
60     private static final State ACTION_OFF_STATE = new StringType(ACTION_OFF);
61     private static final List<String> ACTION_MODES = List.of(ACTION_OFF, "heating", "cooling", "drying", "idle", "fan");
62
63     /**
64      * Configuration class for MQTT component
65      */
66     static class ChannelConfiguration extends AbstractChannelConfiguration {
67         ChannelConfiguration() {
68             super("MQTT HVAC");
69         }
70
71         protected @Nullable String action_template;
72         protected @Nullable String action_topic;
73
74         protected @Nullable String aux_command_topic;
75         protected @Nullable String aux_state_template;
76         protected @Nullable String aux_state_topic;
77
78         protected @Nullable String away_mode_command_topic;
79         protected @Nullable String away_mode_state_template;
80         protected @Nullable String away_mode_state_topic;
81
82         protected @Nullable String current_temperature_template;
83         protected @Nullable String current_temperature_topic;
84
85         protected @Nullable String fan_mode_command_template;
86         protected @Nullable String fan_mode_command_topic;
87         protected @Nullable String fan_mode_state_template;
88         protected @Nullable String fan_mode_state_topic;
89         protected List<String> fan_modes = Arrays.asList("auto", "low", "medium", "high");
90
91         protected @Nullable String hold_command_template;
92         protected @Nullable String hold_command_topic;
93         protected @Nullable String hold_state_template;
94         protected @Nullable String hold_state_topic;
95         protected @Nullable List<String> hold_modes; // Are there default modes? Now the channel will be ignored without
96                                                      // hold modes.
97
98         protected @Nullable String json_attributes_template; // Attributes are not supported yet
99         protected @Nullable String json_attributes_topic;
100
101         protected @Nullable String mode_command_template;
102         protected @Nullable String mode_command_topic;
103         protected @Nullable String mode_state_template;
104         protected @Nullable String mode_state_topic;
105         protected List<String> modes = Arrays.asList("auto", "off", "cool", "heat", "dry", "fan_only");
106
107         protected @Nullable String swing_command_template;
108         protected @Nullable String swing_command_topic;
109         protected @Nullable String swing_state_template;
110         protected @Nullable String swing_state_topic;
111         protected List<String> swing_modes = Arrays.asList("on", "off");
112
113         protected @Nullable String temperature_command_template;
114         protected @Nullable String temperature_command_topic;
115         protected @Nullable String temperature_state_template;
116         protected @Nullable String temperature_state_topic;
117
118         protected @Nullable String temperature_high_command_template;
119         protected @Nullable String temperature_high_command_topic;
120         protected @Nullable String temperature_high_state_template;
121         protected @Nullable String temperature_high_state_topic;
122
123         protected @Nullable String temperature_low_command_template;
124         protected @Nullable String temperature_low_command_topic;
125         protected @Nullable String temperature_low_state_template;
126         protected @Nullable String temperature_low_state_topic;
127
128         protected @Nullable String power_command_topic;
129
130         protected Integer initial = 21;
131         protected @Nullable Float max_temp;
132         protected @Nullable Float min_temp;
133         protected String temperature_unit = CELSIUM; // System unit by default
134         protected Float temp_step = 1f;
135         protected @Nullable Float precision;
136         protected Boolean send_if_off = true;
137     }
138
139     public Climate(ComponentFactory.ComponentConfiguration componentConfiguration) {
140         super(componentConfiguration, ChannelConfiguration.class);
141
142         BigDecimal minTemp = channelConfiguration.min_temp != null ? BigDecimal.valueOf(channelConfiguration.min_temp)
143                 : null;
144         BigDecimal maxTemp = channelConfiguration.max_temp != null ? BigDecimal.valueOf(channelConfiguration.max_temp)
145                 : null;
146         float precision = channelConfiguration.precision != null ? channelConfiguration.precision
147                 : (FAHRENHEIT.equals(channelConfiguration.temperature_unit) ? DEFAULT_FAHRENHEIT_PRECISION
148                         : DEFAULT_CELSIUM_PRECISION);
149         final ChannelStateUpdateListener updateListener = componentConfiguration.getUpdateListener();
150
151         ComponentChannel actionChannel = buildOptionalChannel(ACTION_CH_ID,
152                 new TextValue(ACTION_MODES.toArray(new String[0])), updateListener, null, null,
153                 channelConfiguration.action_template, channelConfiguration.action_topic, null);
154
155         final Predicate<Command> commandFilter = channelConfiguration.send_if_off ? null
156                 : getCommandFilter(actionChannel);
157
158         buildOptionalChannel(AUX_CH_ID, new OnOffValue(), updateListener, null, channelConfiguration.aux_command_topic,
159                 channelConfiguration.aux_state_template, channelConfiguration.aux_state_topic, commandFilter);
160
161         buildOptionalChannel(AWAY_MODE_CH_ID, new OnOffValue(), updateListener, null,
162                 channelConfiguration.away_mode_command_topic, channelConfiguration.away_mode_state_template,
163                 channelConfiguration.away_mode_state_topic, commandFilter);
164
165         buildOptionalChannel(CURRENT_TEMPERATURE_CH_ID,
166                 new NumberValue(minTemp, maxTemp, BigDecimal.valueOf(precision), channelConfiguration.temperature_unit),
167                 updateListener, null, null, channelConfiguration.current_temperature_template,
168                 channelConfiguration.current_temperature_topic, commandFilter);
169
170         buildOptionalChannel(FAN_MODE_CH_ID, new TextValue(channelConfiguration.fan_modes.toArray(new String[0])),
171                 updateListener, channelConfiguration.fan_mode_command_template,
172                 channelConfiguration.fan_mode_command_topic, channelConfiguration.fan_mode_state_template,
173                 channelConfiguration.fan_mode_state_topic, commandFilter);
174
175         if (channelConfiguration.hold_modes != null && !channelConfiguration.hold_modes.isEmpty()) {
176             buildOptionalChannel(HOLD_CH_ID, new TextValue(channelConfiguration.hold_modes.toArray(new String[0])),
177                     updateListener, channelConfiguration.hold_command_template, channelConfiguration.hold_command_topic,
178                     channelConfiguration.hold_state_template, channelConfiguration.hold_state_topic, commandFilter);
179         }
180
181         buildOptionalChannel(MODE_CH_ID, new TextValue(channelConfiguration.modes.toArray(new String[0])),
182                 updateListener, channelConfiguration.mode_command_template, channelConfiguration.mode_command_topic,
183                 channelConfiguration.mode_state_template, channelConfiguration.mode_state_topic, commandFilter);
184
185         buildOptionalChannel(SWING_CH_ID, new TextValue(channelConfiguration.swing_modes.toArray(new String[0])),
186                 updateListener, channelConfiguration.swing_command_template, channelConfiguration.swing_command_topic,
187                 channelConfiguration.swing_state_template, channelConfiguration.swing_state_topic, commandFilter);
188
189         buildOptionalChannel(TEMPERATURE_CH_ID,
190                 new NumberValue(minTemp, maxTemp, BigDecimal.valueOf(channelConfiguration.temp_step),
191                         channelConfiguration.temperature_unit),
192                 updateListener, channelConfiguration.temperature_command_template,
193                 channelConfiguration.temperature_command_topic, channelConfiguration.temperature_state_template,
194                 channelConfiguration.temperature_state_topic, commandFilter);
195
196         buildOptionalChannel(TEMPERATURE_HIGH_CH_ID,
197                 new NumberValue(minTemp, maxTemp, BigDecimal.valueOf(channelConfiguration.temp_step),
198                         channelConfiguration.temperature_unit),
199                 updateListener, channelConfiguration.temperature_high_command_template,
200                 channelConfiguration.temperature_high_command_topic,
201                 channelConfiguration.temperature_high_state_template, channelConfiguration.temperature_high_state_topic,
202                 commandFilter);
203
204         buildOptionalChannel(TEMPERATURE_LOW_CH_ID,
205                 new NumberValue(minTemp, maxTemp, BigDecimal.valueOf(channelConfiguration.temp_step),
206                         channelConfiguration.temperature_unit),
207                 updateListener, channelConfiguration.temperature_low_command_template,
208                 channelConfiguration.temperature_low_command_topic, channelConfiguration.temperature_low_state_template,
209                 channelConfiguration.temperature_low_state_topic, commandFilter);
210
211         buildOptionalChannel(POWER_CH_ID, new OnOffValue(), updateListener, null,
212                 channelConfiguration.power_command_topic, null, null, null);
213     }
214
215     @Nullable
216     private ComponentChannel buildOptionalChannel(String channelId, Value valueState,
217             ChannelStateUpdateListener channelStateUpdateListener, @Nullable String commandTemplate,
218             @Nullable String commandTopic, @Nullable String stateTemplate, @Nullable String stateTopic,
219             @Nullable Predicate<Command> commandFilter) {
220         if ((commandTopic != null && !commandTopic.isBlank()) || (stateTopic != null && !stateTopic.isBlank())) {
221             return buildChannel(channelId, valueState, channelConfiguration.getName(), channelStateUpdateListener)
222                     .stateTopic(stateTopic, stateTemplate, channelConfiguration.getValueTemplate())
223                     .commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos(),
224                             commandTemplate)
225                     .commandFilter(commandFilter).build();
226         }
227         return null;
228     }
229
230     private @Nullable Predicate<Command> getCommandFilter(@Nullable ComponentChannel actionChannel) {
231         if (actionChannel == null) {
232             return null;
233         }
234         final var val = actionChannel.getState().getCache();
235         return command -> !ACTION_OFF_STATE.equals(val.getChannelState());
236     }
237 }