2 * Copyright (c) 2010-2020 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.digitalstrom.internal.handler;
15 import static org.openhab.binding.digitalstrom.internal.DigitalSTROMBindingConstants.BINDING_ID;
17 import java.util.ArrayList;
18 import java.util.Arrays;
19 import java.util.HashSet;
20 import java.util.List;
24 import org.openhab.binding.digitalstrom.internal.DigitalSTROMBindingConstants;
25 import org.openhab.binding.digitalstrom.internal.lib.climate.TemperatureControlSensorTransmitter;
26 import org.openhab.binding.digitalstrom.internal.lib.climate.constants.ControlModes;
27 import org.openhab.binding.digitalstrom.internal.lib.climate.constants.ControlStates;
28 import org.openhab.binding.digitalstrom.internal.lib.climate.jsonresponsecontainer.impl.TemperatureControlStatus;
29 import org.openhab.binding.digitalstrom.internal.lib.listener.TemperatureControlStatusListener;
30 import org.openhab.binding.digitalstrom.internal.lib.manager.StructureManager;
31 import org.openhab.binding.digitalstrom.internal.lib.manager.impl.TemperatureControlManager;
32 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.FunctionalColorGroupEnum;
33 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.OutputModeEnum;
34 import org.openhab.binding.digitalstrom.internal.providers.DsChannelTypeProvider;
35 import org.openhab.core.config.core.Configuration;
36 import org.openhab.core.library.types.DecimalType;
37 import org.openhab.core.library.types.IncreaseDecreaseType;
38 import org.openhab.core.library.types.OnOffType;
39 import org.openhab.core.library.types.PercentType;
40 import org.openhab.core.thing.Bridge;
41 import org.openhab.core.thing.Channel;
42 import org.openhab.core.thing.ChannelUID;
43 import org.openhab.core.thing.Thing;
44 import org.openhab.core.thing.ThingStatus;
45 import org.openhab.core.thing.ThingStatusDetail;
46 import org.openhab.core.thing.ThingStatusInfo;
47 import org.openhab.core.thing.ThingTypeUID;
48 import org.openhab.core.thing.binding.BaseThingHandler;
49 import org.openhab.core.thing.binding.ThingHandler;
50 import org.openhab.core.thing.binding.builder.ChannelBuilder;
51 import org.openhab.core.thing.binding.builder.ThingBuilder;
52 import org.openhab.core.thing.type.ChannelTypeUID;
53 import org.openhab.core.types.Command;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
58 * The {@link ZoneTemperatureControlHandler} is responsible for handling the configuration, to load the supported
60 * digitalSTROM zone, which has a temperature control configured, and handling commands, which are sent to the channel.
63 * For that it uses the {@link BridgeHandler} to register itself as {@link TemperatureControlStatusListener} at the
64 * {@link TemperatureControlManager} to get informed by status changes. Through the registration as
65 * {@link TemperatureControlStatusListener} a {@link TemperatureControlSensorTransmitter} will be registered to this
66 * {@link ZoneTemperatureControlHandler}, which is needed to set the temperature or the control value of a zone.
68 * @author Michael Ochel - Initial contribution
69 * @author Matthias Siegele - Initial contribution
71 public class ZoneTemperatureControlHandler extends BaseThingHandler implements TemperatureControlStatusListener {
74 * Contains all supported thing types of this handler
76 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = new HashSet<>(
77 Arrays.asList(DigitalSTROMBindingConstants.THING_TYPE_ZONE_TEMERATURE_CONTROL));
79 private final Logger logger = LoggerFactory.getLogger(ZoneTemperatureControlHandler.class);
81 private TemperatureControlSensorTransmitter temperatureSensorTransmitter;
83 private BridgeHandler dssBridgeHandler;
84 private Integer zoneID;
85 private String currentChannelID;
86 private Float currentValue = 0f;
87 private final Float step = 1f;
89 // check zoneID error codes
90 public static final int ZONE_ID_NOT_EXISTS = -1;
91 public static final int ZONE_ID_NOT_SET = -2;
92 public static final int BRIDGE_IS_NULL = -3;
95 * Creates a new {@link ZoneTemperatureControlHandler}.
97 * @param thing must not be null
99 public ZoneTemperatureControlHandler(Thing thing) {
104 public void initialize() {
105 logger.debug("Initializing DeviceHandler.");
106 if (getConfig().get(DigitalSTROMBindingConstants.ZONE_ID) != null) {
107 final Bridge bridge = getBridge();
108 if (bridge != null) {
109 bridgeStatusChanged(bridge.getStatusInfo());
111 // Set status to OFFLINE, if no bridge is available e.g. because the bridge has been removed and the
112 // Thing was reinitialized.
113 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Bridge is missing!");
116 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "zoneID is missing");
121 * Returns the configured zoneID of the given {@link Configuration}. If the zoneID does't exist or can't be checked
122 * {@link #ZONE_ID_NOT_EXISTS}, {@link #ZONE_ID_NOT_SET} or {@link #BRIDGE_IS_NULL} will be returned.
124 * @param config the {@link Configuration} to be checked
125 * @param bridge the responsible {@link BridgeHandler}
126 * @return zoneID the existing dS zoneID or a error constant
128 public static int getZoneID(Configuration config, BridgeHandler bridge) {
129 if (config == null || config.get(DigitalSTROMBindingConstants.ZONE_ID) == null) {
130 return ZONE_ID_NOT_SET;
132 if (bridge == null) {
133 return BRIDGE_IS_NULL;
135 String configZoneID = config.get(DigitalSTROMBindingConstants.ZONE_ID).toString();
137 StructureManager strucMan = bridge.getStructureManager();
138 if (strucMan != null) {
140 zoneID = Integer.parseInt(configZoneID);
141 if (!strucMan.checkZoneID(zoneID)) {
142 zoneID = ZONE_ID_NOT_EXISTS;
144 } catch (NumberFormatException e) {
145 zoneID = strucMan.getZoneId(configZoneID);
149 return ZONE_ID_NOT_EXISTS;
153 public void dispose() {
154 logger.debug("Handler disposed... unregister DeviceStatusListener");
155 if (zoneID != null) {
156 if (dssBridgeHandler != null) {
157 dssBridgeHandler.unregisterTemperatureControlStatusListener(this);
159 temperatureSensorTransmitter = null;
164 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
165 if (bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
166 int tempZoneID = getZoneID(getConfig(), getDssBridgeHandler());
167 if (tempZoneID == ZONE_ID_NOT_EXISTS) {
168 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
169 "Configured zone '" + getConfig().get(DigitalSTROMBindingConstants.ZONE_ID)
170 + "' does not exist, please check the configuration.");
172 this.zoneID = tempZoneID;
174 if (zoneID != null) {
175 if (getDssBridgeHandler() != null && temperatureSensorTransmitter == null) {
176 dssBridgeHandler.registerTemperatureControlStatusListener(this);
177 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
178 "waiting for listener registration");
180 updateStatus(ThingStatus.ONLINE);
183 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No zoneID is set!");
186 if (bridgeStatusInfo.getStatus().equals(ThingStatus.OFFLINE)) {
187 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
189 if (bridgeStatusInfo.getStatus().equals(ThingStatus.REMOVED)) {
190 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Bridge has been removed.");
192 logger.debug("Set status to {}", getThing().getStatusInfo());
196 public void handleCommand(ChannelUID channelUID, Command command) {
197 BridgeHandler dssBridgeHandler = getDssBridgeHandler();
198 if (dssBridgeHandler == null) {
199 logger.debug("BridgeHandler not found. Cannot handle command without bridge.");
202 if (temperatureSensorTransmitter == null && zoneID != null) {
204 "Device not known on TemperationControlManager or temperatureSensorTransreciver is not registerd. Cannot handle command.");
207 if (channelUID.getId().equals(currentChannelID)) {
208 if (command instanceof PercentType || command instanceof DecimalType) {
209 sendCommandAndUpdateChannel(((DecimalType) command).floatValue());
210 } else if (command instanceof OnOffType) {
211 if (OnOffType.ON.equals(command)) {
212 if (isTemperature()) {
213 sendCommandAndUpdateChannel(TemperatureControlSensorTransmitter.MAX_TEMP);
215 sendCommandAndUpdateChannel(TemperatureControlSensorTransmitter.MAX_CONTROLL_VALUE);
218 if (isTemperature()) {
219 sendCommandAndUpdateChannel(0f);
221 sendCommandAndUpdateChannel(TemperatureControlSensorTransmitter.MIN_CONTROLL_VALUE);
224 } else if (command instanceof IncreaseDecreaseType) {
225 if (IncreaseDecreaseType.INCREASE.equals(command)) {
226 sendCommandAndUpdateChannel(currentValue + step);
228 sendCommandAndUpdateChannel(currentValue - step);
232 logger.debug("Command sent to an unknown channel id: {}", channelUID);
236 private boolean isTemperature() {
237 return currentChannelID.contains(DsChannelTypeProvider.TEMPERATURE_CONTROLLED);
240 private void sendCommandAndUpdateChannel(Float newValue) {
241 if (isTemperature()) {
242 if (temperatureSensorTransmitter.pushTargetTemperature(zoneID, newValue)) {
243 currentValue = newValue;
244 updateState(currentChannelID, new DecimalType(newValue));
247 if (temperatureSensorTransmitter.pushControlValue(zoneID, newValue)) {
248 currentValue = newValue;
249 updateState(currentChannelID, new PercentType(newValue.intValue()));
254 private synchronized BridgeHandler getDssBridgeHandler() {
255 if (this.dssBridgeHandler == null) {
256 Bridge bridge = getBridge();
257 if (bridge == null) {
258 logger.debug("Bride cannot be found");
261 ThingHandler handler = bridge.getHandler();
263 if (handler instanceof BridgeHandler) {
264 dssBridgeHandler = (BridgeHandler) handler;
269 return dssBridgeHandler;
273 public synchronized void configChanged(TemperatureControlStatus tempControlStatus) {
274 if (tempControlStatus != null && tempControlStatus.isNotSetOff()) {
275 ControlModes controlMode = ControlModes.getControlMode(tempControlStatus.getControlMode());
276 ControlStates controlState = ControlStates.getControlState(tempControlStatus.getControlState());
277 if (controlMode != null && controlState != null) {
278 logger.debug("config changed: {}", tempControlStatus.toString());
279 if (controlMode.equals(ControlModes.OFF) && currentChannelID != null) {
280 currentChannelID = null;
282 } else if (controlMode.equals(ControlModes.PID_CONTROL)
283 && (currentChannelID == null
284 || !currentChannelID.contains(DsChannelTypeProvider.TEMPERATURE_CONTROLLED))
285 && !controlState.equals(ControlStates.EMERGENCY)) {
286 currentChannelID = DsChannelTypeProvider.getOutputChannelTypeID(FunctionalColorGroupEnum.BLUE,
287 OutputModeEnum.TEMPRETURE_PWM);
289 currentValue = tempControlStatus.getNominalValue();
290 updateState(currentChannelID, new DecimalType(currentValue.doubleValue()));
291 } else if (!controlMode.equals(ControlModes.PID_CONTROL) && !controlMode.equals(ControlModes.OFF)) {
292 currentChannelID = DsChannelTypeProvider.getOutputChannelTypeID(FunctionalColorGroupEnum.BLUE,
293 OutputModeEnum.HEATING_PWM);
295 currentValue = tempControlStatus.getControlValue();
296 updateState(currentChannelID, new PercentType(fixPercent(currentValue.intValue())));
297 if (controlState.equals(ControlStates.EMERGENCY)) {
298 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.COMMUNICATION_ERROR,
299 "The communication with temperation sensor fails. Temperature control state emergency (temperature control though the control value) is active.");
302 Map<String, String> properties = editProperties();
303 properties.put("controlDSUID", tempControlStatus.getControlDSUID());
304 properties.put("controlMode", controlMode.getKey());
305 properties.put("controlState", controlState.getKey());
306 updateProperties(properties);
309 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
310 "digitalSTROM temperature control is for this zone not configured in.");
314 private synchronized void loadChannel() {
315 List<Channel> newChannelList = new ArrayList<>(1);
316 if (currentChannelID != null) {
317 newChannelList.add(ChannelBuilder
318 .create(new ChannelUID(this.getThing().getUID(), currentChannelID),
319 DsChannelTypeProvider.getItemType(currentChannelID))
320 .withType(new ChannelTypeUID(BINDING_ID, currentChannelID)).build());
322 ThingBuilder thingBuilder = editThing();
323 thingBuilder.withChannels(newChannelList);
324 updateThing(thingBuilder.build());
325 logger.debug("load channel: {} with item: {}", currentChannelID,
326 DsChannelTypeProvider.getItemType(currentChannelID));
330 public synchronized void onTargetTemperatureChanged(Float newValue) {
331 if (isTemperature()) {
332 updateState(currentChannelID, new DecimalType(newValue));
337 public synchronized void onControlValueChanged(Integer newValue) {
338 if (!isTemperature()) {
339 updateState(currentChannelID, new PercentType(fixPercent(newValue)));
343 private int fixPercent(int value) {
344 return value < 0 ? 0 : value > 100 ? 100 : value;
348 public void registerTemperatureSensorTransmitter(
349 TemperatureControlSensorTransmitter temperatureSensorTransreciver) {
350 updateStatus(ThingStatus.ONLINE);
351 this.temperatureSensorTransmitter = temperatureSensorTransreciver;
355 public Integer getTemperationControlStatusListenrID() {