]> git.basschouten.com Git - openhab-addons.git/blob
d390f9645781114c221392cae4461de54ebc13a2
[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.digitalstrom.internal.handler;
14
15 import static org.openhab.binding.digitalstrom.internal.DigitalSTROMBindingConstants.BINDING_ID;
16
17 import java.util.ArrayList;
18 import java.util.Arrays;
19 import java.util.HashSet;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Set;
23
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.ApplicationGroup;
33 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.OutputChannelEnum;
34 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.OutputModeEnum;
35 import org.openhab.binding.digitalstrom.internal.providers.DsChannelTypeProvider;
36 import org.openhab.core.config.core.Configuration;
37 import org.openhab.core.library.types.DecimalType;
38 import org.openhab.core.library.types.IncreaseDecreaseType;
39 import org.openhab.core.library.types.OnOffType;
40 import org.openhab.core.library.types.PercentType;
41 import org.openhab.core.thing.Bridge;
42 import org.openhab.core.thing.Channel;
43 import org.openhab.core.thing.ChannelUID;
44 import org.openhab.core.thing.Thing;
45 import org.openhab.core.thing.ThingStatus;
46 import org.openhab.core.thing.ThingStatusDetail;
47 import org.openhab.core.thing.ThingStatusInfo;
48 import org.openhab.core.thing.ThingTypeUID;
49 import org.openhab.core.thing.binding.BaseThingHandler;
50 import org.openhab.core.thing.binding.ThingHandler;
51 import org.openhab.core.thing.binding.builder.ChannelBuilder;
52 import org.openhab.core.thing.binding.builder.ThingBuilder;
53 import org.openhab.core.thing.type.ChannelTypeUID;
54 import org.openhab.core.types.Command;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57
58 /**
59  * The {@link ZoneTemperatureControlHandler} is responsible for handling the configuration, to load the supported
60  * channel of a
61  * digitalSTROM zone, which has a temperature control configured, and handling commands, which are sent to the channel.
62  * <br>
63  * <br>
64  * For that it uses the {@link BridgeHandler} to register itself as {@link TemperatureControlStatusListener} at the
65  * {@link TemperatureControlManager} to get informed by status changes. Through the registration as
66  * {@link TemperatureControlStatusListener} a {@link TemperatureControlSensorTransmitter} will be registered to this
67  * {@link ZoneTemperatureControlHandler}, which is needed to set the temperature or the control value of a zone.
68  *
69  * @author Michael Ochel - Initial contribution
70  * @author Matthias Siegele - Initial contribution
71  */
72 public class ZoneTemperatureControlHandler extends BaseThingHandler implements TemperatureControlStatusListener {
73
74     /**
75      * Contains all supported thing types of this handler
76      */
77     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = new HashSet<>(
78             Arrays.asList(DigitalSTROMBindingConstants.THING_TYPE_ZONE_TEMERATURE_CONTROL));
79
80     private final Logger logger = LoggerFactory.getLogger(ZoneTemperatureControlHandler.class);
81
82     private TemperatureControlSensorTransmitter temperatureSensorTransmitter;
83
84     private BridgeHandler dssBridgeHandler;
85     private Integer zoneID;
86     private String currentChannelID;
87     private Float currentValue = 0f;
88     private final Float step = 1f;
89
90     // check zoneID error codes
91     public static final int ZONE_ID_NOT_EXISTS = -1;
92     public static final int ZONE_ID_NOT_SET = -2;
93     public static final int BRIDGE_IS_NULL = -3;
94
95     /**
96      * Creates a new {@link ZoneTemperatureControlHandler}.
97      *
98      * @param thing must not be null
99      */
100     public ZoneTemperatureControlHandler(Thing thing) {
101         super(thing);
102     }
103
104     @Override
105     public void initialize() {
106         logger.debug("Initializing DeviceHandler.");
107         if (getConfig().get(DigitalSTROMBindingConstants.ZONE_ID) != null) {
108             final Bridge bridge = getBridge();
109             if (bridge != null) {
110                 bridgeStatusChanged(bridge.getStatusInfo());
111             } else {
112                 // Set status to OFFLINE, if no bridge is available e.g. because the bridge has been removed and the
113                 // Thing was reinitialized.
114                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Bridge is missing!");
115             }
116         } else {
117             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "zoneID is missing");
118         }
119     }
120
121     /**
122      * Returns the configured zoneID of the given {@link Configuration}. If the zoneID does't exist or can't be checked
123      * {@link #ZONE_ID_NOT_EXISTS}, {@link #ZONE_ID_NOT_SET} or {@link #BRIDGE_IS_NULL} will be returned.
124      *
125      * @param config the {@link Configuration} to be checked
126      * @param bridge the responsible {@link BridgeHandler}
127      * @return zoneID the existing dS zoneID or an error constant
128      */
129     public static int getZoneID(Configuration config, BridgeHandler bridge) {
130         if (config == null || config.get(DigitalSTROMBindingConstants.ZONE_ID) == null) {
131             return ZONE_ID_NOT_SET;
132         }
133         if (bridge == null) {
134             return BRIDGE_IS_NULL;
135         }
136         String configZoneID = config.get(DigitalSTROMBindingConstants.ZONE_ID).toString();
137         int zoneID;
138         StructureManager strucMan = bridge.getStructureManager();
139         if (strucMan != null) {
140             try {
141                 zoneID = Integer.parseInt(configZoneID);
142                 if (!strucMan.checkZoneID(zoneID)) {
143                     zoneID = ZONE_ID_NOT_EXISTS;
144                 }
145             } catch (NumberFormatException e) {
146                 zoneID = strucMan.getZoneId(configZoneID);
147             }
148             return zoneID;
149         }
150         return ZONE_ID_NOT_EXISTS;
151     }
152
153     @Override
154     public void dispose() {
155         logger.debug("Handler disposed... unregister DeviceStatusListener");
156         if (zoneID != null) {
157             if (dssBridgeHandler != null) {
158                 dssBridgeHandler.unregisterTemperatureControlStatusListener(this);
159             }
160             temperatureSensorTransmitter = null;
161         }
162     }
163
164     @Override
165     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
166         if (bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
167             int tempZoneID = getZoneID(getConfig(), getDssBridgeHandler());
168             if (tempZoneID == ZONE_ID_NOT_EXISTS) {
169                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
170                         "Configured zone '" + getConfig().get(DigitalSTROMBindingConstants.ZONE_ID)
171                                 + "' does not exist, please check the configuration.");
172             } else {
173                 this.zoneID = tempZoneID;
174             }
175             if (zoneID != null) {
176                 if (getDssBridgeHandler() != null && temperatureSensorTransmitter == null) {
177                     dssBridgeHandler.registerTemperatureControlStatusListener(this);
178                     updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
179                             "waiting for listener registration");
180                 } else {
181                     updateStatus(ThingStatus.ONLINE);
182                 }
183             } else {
184                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No zoneID is set!");
185             }
186         }
187         if (bridgeStatusInfo.getStatus().equals(ThingStatus.OFFLINE)) {
188             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
189         }
190         logger.debug("Set status to {}", getThing().getStatusInfo());
191     }
192
193     @Override
194     public void handleCommand(ChannelUID channelUID, Command command) {
195         BridgeHandler dssBridgeHandler = getDssBridgeHandler();
196         if (dssBridgeHandler == null) {
197             logger.debug("BridgeHandler not found. Cannot handle command without bridge.");
198             return;
199         }
200         if (temperatureSensorTransmitter == null && zoneID != null) {
201             logger.debug(
202                     "Device not known on TemperationControlManager or temperatureSensorTransreciver is not registerd. Cannot handle command.");
203             return;
204         }
205         if (channelUID.getId().equals(currentChannelID)) {
206             if (command instanceof PercentType || command instanceof DecimalType) {
207                 sendCommandAndUpdateChannel(((DecimalType) command).floatValue());
208             } else if (command instanceof OnOffType) {
209                 if (OnOffType.ON.equals(command)) {
210                     if (isTemperature()) {
211                         sendCommandAndUpdateChannel(TemperatureControlSensorTransmitter.MAX_TEMP);
212                     } else {
213                         sendCommandAndUpdateChannel(TemperatureControlSensorTransmitter.MAX_CONTROLL_VALUE);
214                     }
215                 } else {
216                     if (isTemperature()) {
217                         sendCommandAndUpdateChannel(0f);
218                     } else {
219                         sendCommandAndUpdateChannel(TemperatureControlSensorTransmitter.MIN_CONTROLL_VALUE);
220                     }
221                 }
222             } else if (command instanceof IncreaseDecreaseType) {
223                 if (IncreaseDecreaseType.INCREASE.equals(command)) {
224                     sendCommandAndUpdateChannel(currentValue + step);
225                 } else {
226                     sendCommandAndUpdateChannel(currentValue - step);
227                 }
228             }
229         } else {
230             logger.debug("Command sent to an unknown channel id: {}", channelUID);
231         }
232     }
233
234     private boolean isTemperature() {
235         return currentChannelID.contains(DsChannelTypeProvider.TEMPERATURE_CONTROLLED);
236     }
237
238     private void sendCommandAndUpdateChannel(Float newValue) {
239         if (isTemperature()) {
240             if (temperatureSensorTransmitter.pushTargetTemperature(zoneID, newValue)) {
241                 currentValue = newValue;
242                 updateState(currentChannelID, new DecimalType(newValue));
243             }
244         } else {
245             if (temperatureSensorTransmitter.pushControlValue(zoneID, newValue)) {
246                 currentValue = newValue;
247                 updateState(currentChannelID, new PercentType(newValue.intValue()));
248             }
249         }
250     }
251
252     private synchronized BridgeHandler getDssBridgeHandler() {
253         if (this.dssBridgeHandler == null) {
254             Bridge bridge = getBridge();
255             if (bridge == null) {
256                 logger.debug("Bride cannot be found");
257                 return null;
258             }
259             ThingHandler handler = bridge.getHandler();
260
261             if (handler instanceof BridgeHandler) {
262                 dssBridgeHandler = (BridgeHandler) handler;
263             } else {
264                 return null;
265             }
266         }
267         return dssBridgeHandler;
268     }
269
270     @Override
271     public synchronized void configChanged(TemperatureControlStatus tempControlStatus) {
272         if (tempControlStatus != null && tempControlStatus.isNotSetOff()) {
273             ControlModes controlMode = ControlModes.getControlMode(tempControlStatus.getControlMode());
274             ControlStates controlState = ControlStates.getControlState(tempControlStatus.getControlState());
275             if (controlMode != null && controlState != null) {
276                 logger.debug("config changed: {}", tempControlStatus.toString());
277                 if (controlMode.equals(ControlModes.OFF) && currentChannelID != null) {
278                     currentChannelID = null;
279                     loadChannel();
280                 } else if (controlMode.equals(ControlModes.PID_CONTROL)
281                         && (currentChannelID == null
282                                 || !currentChannelID.contains(DsChannelTypeProvider.TEMPERATURE_CONTROLLED))
283                         && !controlState.equals(ControlStates.EMERGENCY)) {
284                     currentChannelID = DsChannelTypeProvider.getOutputChannelTypeID(ApplicationGroup.Color.BLUE,
285                             OutputModeEnum.TEMPRETURE_PWM, new ArrayList<OutputChannelEnum>());
286                     loadChannel();
287                     currentValue = tempControlStatus.getNominalValue();
288                     updateState(currentChannelID, new DecimalType(currentValue.doubleValue()));
289                 } else if (!controlMode.equals(ControlModes.PID_CONTROL) && !controlMode.equals(ControlModes.OFF)) {
290                     currentChannelID = DsChannelTypeProvider.getOutputChannelTypeID(ApplicationGroup.Color.BLUE,
291                             OutputModeEnum.HEATING_PWM, new ArrayList<OutputChannelEnum>());
292                     loadChannel();
293                     currentValue = tempControlStatus.getControlValue();
294                     updateState(currentChannelID, new PercentType(fixPercent(currentValue.intValue())));
295                     if (controlState.equals(ControlStates.EMERGENCY)) {
296                         updateStatus(ThingStatus.ONLINE, ThingStatusDetail.COMMUNICATION_ERROR,
297                                 "The communication with temperation sensor fails. Temperature control state emergency (temperature control though the control value) is active.");
298                     }
299                 }
300                 Map<String, String> properties = editProperties();
301                 properties.put("controlDSUID", tempControlStatus.getControlDSUID());
302                 properties.put("controlMode", controlMode.getKey());
303                 properties.put("controlState", controlState.getKey());
304                 updateProperties(properties);
305             }
306         } else {
307             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
308                     "digitalSTROM temperature control is for this zone not configured in.");
309         }
310     }
311
312     private synchronized void loadChannel() {
313         List<Channel> newChannelList = new ArrayList<>(1);
314         if (currentChannelID != null) {
315             newChannelList.add(ChannelBuilder
316                     .create(new ChannelUID(this.getThing().getUID(), currentChannelID),
317                             DsChannelTypeProvider.getItemType(currentChannelID))
318                     .withType(new ChannelTypeUID(BINDING_ID, currentChannelID)).build());
319         }
320         ThingBuilder thingBuilder = editThing();
321         thingBuilder.withChannels(newChannelList);
322         updateThing(thingBuilder.build());
323         logger.debug("load channel: {} with item: {}", currentChannelID,
324                 DsChannelTypeProvider.getItemType(currentChannelID));
325     }
326
327     @Override
328     public synchronized void onTargetTemperatureChanged(Float newValue) {
329         if (isTemperature()) {
330             updateState(currentChannelID, new DecimalType(newValue));
331         }
332     }
333
334     @Override
335     public synchronized void onControlValueChanged(Integer newValue) {
336         if (!isTemperature()) {
337             updateState(currentChannelID, new PercentType(fixPercent(newValue)));
338         }
339     }
340
341     private int fixPercent(int value) {
342         return value < 0 ? 0 : value > 100 ? 100 : value;
343     }
344
345     @Override
346     public void registerTemperatureSensorTransmitter(
347             TemperatureControlSensorTransmitter temperatureSensorTransreciver) {
348         updateStatus(ThingStatus.ONLINE);
349         this.temperatureSensorTransmitter = temperatureSensorTransreciver;
350     }
351
352     @Override
353     public Integer getTemperationControlStatusListenrID() {
354         return zoneID;
355     }
356 }