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