]> git.basschouten.com Git - openhab-addons.git/blob
b81f89feb1a39c37daa2b15a5cdfc32723b85c51
[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.lib.manager.impl;
14
15 import java.util.Arrays;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.HashMap;
19 import java.util.LinkedList;
20 import java.util.List;
21
22 import org.openhab.binding.digitalstrom.internal.lib.climate.TemperatureControlSensorTransmitter;
23 import org.openhab.binding.digitalstrom.internal.lib.climate.jsonresponsecontainer.impl.TemperatureControlStatus;
24 import org.openhab.binding.digitalstrom.internal.lib.event.EventHandler;
25 import org.openhab.binding.digitalstrom.internal.lib.event.EventListener;
26 import org.openhab.binding.digitalstrom.internal.lib.event.constants.EventNames;
27 import org.openhab.binding.digitalstrom.internal.lib.event.constants.EventResponseEnum;
28 import org.openhab.binding.digitalstrom.internal.lib.event.types.EventItem;
29 import org.openhab.binding.digitalstrom.internal.lib.listener.SystemStateChangeListener;
30 import org.openhab.binding.digitalstrom.internal.lib.listener.TemperatureControlStatusListener;
31 import org.openhab.binding.digitalstrom.internal.lib.manager.ConnectionManager;
32 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.DsAPI;
33 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.ApplicationGroup;
34 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.SensorEnum;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 /**
39  * The {@link TemperatureControlManager} is responsible for handling the zone temperature control of the digitalSTROM
40  * zones. For that it implements an {@link EventHandler} to get informed by control changes, like the target
41  * temperature.
42  * It also implement the {@link TemperatureControlSensorTransmitter}, so the zone temperature can be set through this
43  * class. <br>
44  * <br>
45  * To check, if the heating-control-app is installed at the digitalSTROM server the static method
46  * {@link #isHeatingControllerInstallated(ConnectionManager)} can be used.<br>
47  * <br>
48  * To get informed by status changes tow listener types can be registered to the {@link TemperatureControlManager}:<br>
49  * {@link TemperatureControlStatusListener}, to get informed by configuration and status changes or as discovery.<br>
50  * {@link SystemStateChangeListener}, to get informed by heating water system changes. The heating system states are
51  * {@link #STATE_HEATING_WATER_SYSTEM_OFF}, {@link #STATE_HEATING_WATER_SYSTEM_COLD_WATER} and
52  * {@link #STATE_HEATING_WATER_SYSTEM_COLD_WATER}<br>
53  * <br>
54  * The {@link TemperatureControlManager} also contains some helpful static constants, like
55  * {@link #GET_HEATING_WATER_SYSTEM_STATE_PATH} to get the current heating water system state through
56  * {@link DsAPI#propertyTreeGetString(String, String)}.
57  *
58  * @author Michael Ochel - initial contributer
59  * @author Matthias Siegele - initial contributer
60  */
61 public class TemperatureControlManager implements EventHandler, TemperatureControlSensorTransmitter {
62
63     private final List<String> SUPPORTED_EVENTS = Arrays.asList(EventNames.HEATING_CONTROL_OPERATION_MODE);
64
65     private final Logger logger = LoggerFactory.getLogger(TemperatureControlManager.class);
66
67     private final ConnectionManager connectionMananager;
68     private final DsAPI dSapi;
69     private final EventListener eventListener;
70     private boolean isConfigured = false;
71
72     private HashMap<Integer, TemperatureControlStatusListener> zoneTemperationControlListenerMap;
73     private HashMap<Integer, TemperatureControlStatus> temperationControlStatus;
74     private TemperatureControlStatusListener discovery;
75     private SystemStateChangeListener systemStateChangeListener;
76
77     /**
78      * Name of the digitalSTROM heating water system state.
79      */
80     public static final String STATE_NAME_HEATING_WATER_SYSTEM = "heating_water_system";
81     /**
82      * digitalSTROM heating water system state as string for off.
83      */
84     public static final String STATE_HEATING_WATER_SYSTEM_OFF = "off"; // val=0
85     /**
86      * digitalSTROM heating water system state as string for hot water.
87      */
88     public static final String STATE_HEATING_WATER_SYSTEM_HOT_WATER = "hot water"; // val=1
89     /**
90      * digitalSTROM heating water system state as string for cold water.
91      */
92     public static final String STATE_HEATING_WATER_SYSTEM_COLD_WATER = "cold water"; // val=2
93
94     /**
95      * Path to get the current digitalSTROM heating water system state through
96      * {@link DsAPI#propertyTreeGetString(String, String)}.
97      */
98     public static final String GET_HEATING_WATER_SYSTEM_STATE_PATH = "/usr/states/heating_water_system/state";
99     /**
100      * Path to get the current digitalSTROM heating controller nodes through
101      * {@link DsAPI#propertyTreeGetString(String, String)}.
102      * Can be used e.g. to check, if the digitalSTROM heating controller app is installed at the digitalSTROM server.
103      */
104     public static final String GET_HEATING_HEATING_CONTROLLER_CHILDREN_PATH = "/scripts/heating-controller/";
105
106     /**
107      * Action for set operation mode at {@link EventNames#HEATING_CONTROL_OPERATION_MODE}.
108      */
109     public static final String SET_OPERATION_MODE = "setOperationMode";
110     /**
111      * Action for evaluate real active mode at {@link EventNames#HEATING_CONTROL_OPERATION_MODE}. Will be called after
112      * {@link #SET_OPERATION_MODE} or if the configuration of a zone temperature control status has changed.
113      */
114     public static final String EVALUATE_REAL_ACTIVE_MODE = "evaluateRealActiveMode";
115
116     private String currentHeatingWaterSystemStage;
117
118     private final List<String> echoBox = Collections.synchronizedList(new LinkedList<>());
119
120     /**
121      * Creates a new {@link TemperatureControlManager}. The {@link ConnectionManager} is needed. The other fields are
122      * only needed, if you want to get automatically informed by status changes through the {@link EventListener} and/or
123      * get informed by new configured zones as discovery.
124      *
125      * @param connectionMananager (must not be null)
126      * @param eventListener (can be null)
127      * @param discovery (can be null)
128      */
129     public TemperatureControlManager(ConnectionManager connectionMananager, EventListener eventListener,
130             TemperatureControlStatusListener discovery) {
131         this(connectionMananager, eventListener, discovery, null);
132     }
133
134     /**
135      * Same constructor like
136      * {@link #TemperatureControlManager(ConnectionManager, EventListener, TemperatureControlStatusListener)}, but it
137      * can be set a {@link SystemStateChangeListener}, too.
138      *
139      * @param connectionMananager (must not be null)
140      * @param eventListener (can be null)
141      * @param discovery (can be null)
142      * @param systemStateChangeListener (can be null)
143      * @see #TemperatureControlManager(ConnectionManager, EventListener, TemperatureControlStatusListener)
144      */
145     public TemperatureControlManager(ConnectionManager connectionMananager, EventListener eventListener,
146             TemperatureControlStatusListener discovery, SystemStateChangeListener systemStateChangeListener) {
147         this.connectionMananager = connectionMananager;
148         this.dSapi = connectionMananager.getDigitalSTROMAPI();
149         this.systemStateChangeListener = systemStateChangeListener;
150         this.discovery = discovery;
151         this.eventListener = eventListener;
152         checkZones();
153         if (eventListener != null) {
154             if (isConfigured) {
155                 SUPPORTED_EVENTS.add(EventNames.ZONE_SENSOR_VALUE);
156                 if (systemStateChangeListener != null) {
157                     SUPPORTED_EVENTS.add(EventNames.STATE_CHANGED);
158                 }
159             }
160             eventListener.addEventHandler(this);
161         }
162     }
163
164     /**
165      * Checks all digitalSTROM zones, if temperature control is configured. If a zone with configured temperature
166      * control is found, it will be stored, the flag for {@link #isConfigured()} will be set to true and the discovery
167      * will be informed, if a discovery is registered.
168      */
169     public void checkZones() {
170         List<TemperatureControlStatus> temperationControlStatus = dSapi
171                 .getApartmentTemperatureControlStatus(connectionMananager.getSessionToken());
172         if (!temperationControlStatus.isEmpty()) {
173             for (TemperatureControlStatus tempConStat : temperationControlStatus) {
174                 addTemperatureControlStatus(tempConStat);
175             }
176             if (isConfigured && systemStateChangeListener != null) {
177                 currentHeatingWaterSystemStage = dSapi.propertyTreeGetString(connectionMananager.getSessionToken(),
178                         GET_HEATING_WATER_SYSTEM_STATE_PATH);
179             }
180         }
181     }
182
183     /**
184      * Returns true, if the digitalSTROM heating controller app is installed.
185      *
186      * @param connectionManager (must not be null)
187      * @return true, if heating controller app is installed, otherwise false
188      */
189     public static boolean isHeatingControllerInstallated(ConnectionManager connectionManager) {
190         return connectionManager.getDigitalSTROMAPI().propertyTreeGetChildren(connectionManager.getSessionToken(),
191                 GET_HEATING_HEATING_CONTROLLER_CHILDREN_PATH) != null;
192     }
193
194     /**
195      * Returns all zone which have temperature controlled configured.
196      *
197      * @return all temperature controlled zones
198      */
199     public Collection<TemperatureControlStatus> getTemperatureControlStatusFromAllZones() {
200         return temperationControlStatus != null ? this.temperationControlStatus.values() : new LinkedList<>();
201     }
202
203     /**
204      * Registers a {@link TemperatureControlStatusListener} for a zone, if the temperation control for this zone is
205      * configured. It can be also register a {@link TemperatureControlStatusListener} as discovery, if the
206      * {@link TemperatureControlStatusListener#getTemperationControlStatusListenrID()} returns
207      * {@link TemperatureControlStatusListener#DISCOVERY}.
208      *
209      * @param temperatureControlStatusListener to register
210      */
211     public void registerTemperatureControlStatusListener(
212             TemperatureControlStatusListener temperatureControlStatusListener) {
213         if (temperatureControlStatusListener != null) {
214             if (temperatureControlStatusListener.getTemperationControlStatusListenrID()
215                     .equals(TemperatureControlStatusListener.DISCOVERY)) {
216                 logger.debug("discovery is registered");
217                 this.discovery = temperatureControlStatusListener;
218                 if (temperationControlStatus != null) {
219                     for (TemperatureControlStatus tempConStat : temperationControlStatus.values()) {
220                         discovery.configChanged(tempConStat);
221                     }
222                 }
223             } else {
224                 if (zoneTemperationControlListenerMap == null) {
225                     zoneTemperationControlListenerMap = new HashMap<>();
226                 }
227                 TemperatureControlStatus tempConStat = checkAndGetTemperatureControlStatus(
228                         temperatureControlStatusListener.getTemperationControlStatusListenrID());
229                 if (tempConStat != null) {
230                     logger.debug("register listener with id {}",
231                             temperatureControlStatusListener.getTemperationControlStatusListenrID());
232                     zoneTemperationControlListenerMap.put(
233                             temperatureControlStatusListener.getTemperationControlStatusListenrID(),
234                             temperatureControlStatusListener);
235                     temperatureControlStatusListener.registerTemperatureSensorTransmitter(this);
236                 }
237                 temperatureControlStatusListener.configChanged(tempConStat);
238             }
239         }
240     }
241
242     /**
243      * Unregisters a {@link TemperatureControlStatusListener}, if it exist.
244      *
245      * @param temperatureControlStatusListener to unregister
246      */
247     public void unregisterTemperatureControlStatusListener(
248             TemperatureControlStatusListener temperatureControlStatusListener) {
249         if (temperatureControlStatusListener != null) {
250             if (temperatureControlStatusListener.getTemperationControlStatusListenrID()
251                     .equals(TemperatureControlStatusListener.DISCOVERY)) {
252                 this.discovery = null;
253                 return;
254             }
255             if (discovery != null && zoneTemperationControlListenerMap
256                     .remove(temperatureControlStatusListener.getTemperationControlStatusListenrID()) != null) {
257                 discovery.configChanged(temperationControlStatus
258                         .get(temperatureControlStatusListener.getTemperationControlStatusListenrID()));
259             }
260         }
261     }
262
263     /**
264      * Returns the {@link TemperatureControlStatus} for the given zone, if the temperature control is configured,
265      * otherwise it will be returned null.
266      *
267      * @param zoneID to check
268      * @return {@link TemperatureControlStatus} if the temperature control is configured, otherwise null
269      */
270     public TemperatureControlStatus checkAndGetTemperatureControlStatus(Integer zoneID) {
271         TemperatureControlStatus tempConStat = this.temperationControlStatus.get(zoneID);
272         if (tempConStat.isNotSetOff()) {
273             return tempConStat;
274         }
275         return null;
276     }
277
278     private boolean isEcho(Integer zoneID, SensorEnum sensorType, Float value) {
279         return echoBox.remove(zoneID + "-" + sensorType.getSensorType() + "-" + value);
280     }
281
282     private void addEcho(Integer zoneID, SensorEnum sensorType, Float value) {
283         echoBox.add(zoneID + "-" + sensorType.getSensorType() + "-" + value);
284     }
285
286     @Override
287     public void handleEvent(EventItem eventItem) {
288         try {
289             logger.debug("detect event: {}", eventItem.toString());
290             if (eventItem.getName().equals(EventNames.ZONE_SENSOR_VALUE)) {
291                 if (zoneTemperationControlListenerMap != null) {
292                     if (SensorEnum.ROOM_TEMPERATURE_SET_POINT.getSensorType().toString()
293                             .equals(eventItem.getProperties().get(EventResponseEnum.SENSOR_TYPE))) {
294                         Integer zoneID = Integer
295                                 .parseInt(eventItem.getSource().getOrDefault(EventResponseEnum.ZONEID, ""));
296                         if (zoneTemperationControlListenerMap.get(zoneID) != null) {
297
298                             Float newValue = Float.parseFloat(
299                                     eventItem.getProperties().getOrDefault(EventResponseEnum.SENSOR_VALUE_FLOAT, ""));
300                             if (!isEcho(zoneID, SensorEnum.ROOM_TEMPERATURE_CONTROL_VARIABLE, newValue)) {
301                                 zoneTemperationControlListenerMap.get(zoneID).onTargetTemperatureChanged(newValue);
302                             }
303                         }
304                     }
305                     if (SensorEnum.ROOM_TEMPERATURE_CONTROL_VARIABLE.getSensorType().toString()
306                             .equals(eventItem.getProperties().get(EventResponseEnum.SENSOR_TYPE))) {
307                         Integer zoneID = Integer
308                                 .parseInt(eventItem.getSource().getOrDefault(EventResponseEnum.ZONEID, ""));
309                         if (zoneTemperationControlListenerMap.get(zoneID) != null) {
310                             Float newValue = Float.parseFloat(
311                                     eventItem.getProperties().getOrDefault(EventResponseEnum.SENSOR_VALUE_FLOAT, ""));
312                             if (!isEcho(zoneID, SensorEnum.ROOM_TEMPERATURE_CONTROL_VARIABLE, newValue)) {
313                                 zoneTemperationControlListenerMap.get(zoneID)
314                                         .onControlValueChanged(newValue.intValue());
315                             }
316                         }
317                     }
318                 }
319             }
320
321             if (eventItem.getName().equals(EventNames.HEATING_CONTROL_OPERATION_MODE)) {
322                 if (EVALUATE_REAL_ACTIVE_MODE.equals(eventItem.getProperties().get(EventResponseEnum.ACTIONS))) {
323                     Integer zoneID = Integer
324                             .parseInt(eventItem.getProperties().getOrDefault(EventResponseEnum.ZONEID, ""));
325                     TemperatureControlStatus temperationControlStatus = dSapi
326                             .getZoneTemperatureControlStatus(connectionMananager.getSessionToken(), zoneID, null);
327                     if (temperationControlStatus != null) {
328                         addTemperatureControlStatus(temperationControlStatus);
329                     }
330                 }
331             }
332
333             if (eventItem.getName().equals(EventNames.STATE_CHANGED)) {
334                 if (STATE_NAME_HEATING_WATER_SYSTEM
335                         .equals(eventItem.getProperties().get(EventResponseEnum.STATE_NAME))) {
336                     currentHeatingWaterSystemStage = eventItem.getProperties().get(EventResponseEnum.STATE);
337                     logger.debug("heating water system state changed to {}", currentHeatingWaterSystemStage);
338                     if (systemStateChangeListener != null) {
339                         systemStateChangeListener.onSystemStateChanged(STATE_NAME_HEATING_WATER_SYSTEM,
340                                 currentHeatingWaterSystemStage);
341                     }
342                 }
343             }
344         } catch (NumberFormatException e) {
345             logger.debug("Unexpected missing or invalid number while handling event", e);
346         }
347     }
348
349     private void addTemperatureControlStatus(TemperatureControlStatus temperationControlStatus) {
350         if (temperationControlStatus.isNotSetOff()) {
351             if (this.temperationControlStatus == null) {
352                 this.temperationControlStatus = new HashMap<>();
353             }
354             if (this.temperationControlStatus.get(temperationControlStatus.getZoneID()) == null && discovery != null) {
355                 discovery.configChanged(temperationControlStatus);
356                 if (!isConfigured) {
357                     isConfigured = true;
358                 }
359             }
360             this.temperationControlStatus.put(temperationControlStatus.getZoneID(), temperationControlStatus);
361             if (zoneTemperationControlListenerMap != null
362                     && zoneTemperationControlListenerMap.get(temperationControlStatus.getZoneID()) != null) {
363                 zoneTemperationControlListenerMap.get(temperationControlStatus.getZoneID())
364                         .configChanged(temperationControlStatus);
365             }
366         }
367     }
368
369     @Override
370     public List<String> getSupportedEvents() {
371         return SUPPORTED_EVENTS;
372     }
373
374     @Override
375     public boolean supportsEvent(String eventName) {
376         return SUPPORTED_EVENTS.contains(eventName);
377     }
378
379     @Override
380     public String getUID() {
381         return getClass().getSimpleName();
382     }
383
384     @Override
385     public void setEventListener(EventListener eventListener) {
386         eventListener.addEventHandler(this);
387     }
388
389     @Override
390     public void unsetEventListener(EventListener eventListener) {
391         eventListener.removeEventHandler(this);
392     }
393
394     @Override
395     public boolean pushTargetTemperature(Integer zoneID, Float newValue) {
396         if (checkAndGetTemperatureControlStatus(zoneID) != null) {
397             if (dSapi.pushZoneSensorValue(connectionMananager.getSessionToken(), zoneID, null, (short) 0, null,
398                     newValue, SensorEnum.ROOM_TEMPERATURE_SET_POINT)) {
399                 addEcho(zoneID, SensorEnum.ROOM_TEMPERATURE_SET_POINT, newValue);
400                 return true;
401             }
402         }
403         return false;
404     }
405
406     @Override
407     public boolean pushControlValue(Integer zoneID, Float newValue) {
408         if (checkAndGetTemperatureControlStatus(zoneID) != null) {
409             if (dSapi.pushZoneSensorValue(connectionMananager.getSessionToken(), zoneID, null,
410                     ApplicationGroup.TEMPERATURE_CONTROL.getId(), null, newValue,
411                     SensorEnum.ROOM_TEMPERATURE_CONTROL_VARIABLE)) {
412                 addEcho(zoneID, SensorEnum.ROOM_TEMPERATURE_CONTROL_VARIABLE, newValue);
413                 return true;
414             }
415         }
416         return false;
417     }
418
419     /**
420      * Returns true, if minimum one zone has temperature control configured.
421      *
422      * @return true, if minimum one zone has temperature control configured, otherwise false
423      */
424     public boolean isConfigured() {
425         return isConfigured;
426     }
427
428     /**
429      * Returns the current heating water system state, if a {@link SystemStateChangeListener} is registered, otherwise
430      * null.
431      *
432      * @return the current heating water system state or null, if no {@link SystemStateChangeListener}
433      */
434     public String getHeatingWaterSystemState() {
435         return currentHeatingWaterSystemStage;
436     }
437
438     /**
439      * Registers the given {@link SystemStateChangeListener}, which will be informed about heating system water state
440      * changes.
441      *
442      * @param systemStateChangeListener to register
443      */
444     public void registerSystemStateChangeListener(SystemStateChangeListener systemStateChangeListener) {
445         if (eventListener != null) {
446             SUPPORTED_EVENTS.add(EventNames.STATE_CHANGED);
447             eventListener.addSubscribe(EventNames.STATE_CHANGED);
448         }
449         this.systemStateChangeListener = systemStateChangeListener;
450     }
451
452     /**
453      * Unregisters a registered {@link SystemStateChangeListener}.
454      */
455     public void unregisterSystemStateChangeListener() {
456         this.systemStateChangeListener = null;
457     }
458 }