2 * Copyright (c) 2010-2024 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.lib.manager.impl;
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;
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;
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
42 * It also implement the {@link TemperatureControlSensorTransmitter}, so the zone temperature can be set through this
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>
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>
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)}.
58 * @author Michael Ochel - Initial contribution
59 * @author Matthias Siegele - Initial contribution
61 public class TemperatureControlManager implements EventHandler, TemperatureControlSensorTransmitter {
63 private static final List<String> SUPPORTED_EVENTS = Arrays.asList(EventNames.HEATING_CONTROL_OPERATION_MODE);
65 private final Logger logger = LoggerFactory.getLogger(TemperatureControlManager.class);
67 private final ConnectionManager connectionMananager;
68 private final DsAPI dSapi;
69 private final EventListener eventListener;
70 private boolean isConfigured = false;
72 private HashMap<Integer, TemperatureControlStatusListener> zoneTemperationControlListenerMap;
73 private HashMap<Integer, TemperatureControlStatus> temperationControlStatus;
74 private TemperatureControlStatusListener discovery;
75 private SystemStateChangeListener systemStateChangeListener;
78 * Name of the digitalSTROM heating water system state.
80 public static final String STATE_NAME_HEATING_WATER_SYSTEM = "heating_water_system";
82 * digitalSTROM heating water system state as string for off.
84 public static final String STATE_HEATING_WATER_SYSTEM_OFF = "off"; // val=0
86 * digitalSTROM heating water system state as string for hot water.
88 public static final String STATE_HEATING_WATER_SYSTEM_HOT_WATER = "hot water"; // val=1
90 * digitalSTROM heating water system state as string for cold water.
92 public static final String STATE_HEATING_WATER_SYSTEM_COLD_WATER = "cold water"; // val=2
95 * Path to get the current digitalSTROM heating water system state through
96 * {@link DsAPI#propertyTreeGetString(String, String)}.
98 public static final String GET_HEATING_WATER_SYSTEM_STATE_PATH = "/usr/states/heating_water_system/state";
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.
104 public static final String GET_HEATING_HEATING_CONTROLLER_CHILDREN_PATH = "/scripts/heating-controller/";
107 * Action for set operation mode at {@link EventNames#HEATING_CONTROL_OPERATION_MODE}.
109 public static final String SET_OPERATION_MODE = "setOperationMode";
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.
114 public static final String EVALUATE_REAL_ACTIVE_MODE = "evaluateRealActiveMode";
116 private String currentHeatingWaterSystemStage;
118 private final List<String> echoBox = Collections.synchronizedList(new LinkedList<>());
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.
125 * @param connectionMananager (must not be null)
126 * @param eventListener (can be null)
127 * @param discovery (can be null)
129 public TemperatureControlManager(ConnectionManager connectionMananager, EventListener eventListener,
130 TemperatureControlStatusListener discovery) {
131 this(connectionMananager, eventListener, discovery, null);
135 * Same constructor like
136 * {@link #TemperatureControlManager(ConnectionManager, EventListener, TemperatureControlStatusListener)}, but it
137 * can be set a {@link SystemStateChangeListener}, too.
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)
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;
153 if (eventListener != null) {
155 SUPPORTED_EVENTS.add(EventNames.ZONE_SENSOR_VALUE);
156 if (systemStateChangeListener != null) {
157 SUPPORTED_EVENTS.add(EventNames.STATE_CHANGED);
160 eventListener.addEventHandler(this);
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.
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);
176 if (isConfigured && systemStateChangeListener != null) {
177 currentHeatingWaterSystemStage = dSapi.propertyTreeGetString(connectionMananager.getSessionToken(),
178 GET_HEATING_WATER_SYSTEM_STATE_PATH);
184 * Returns true, if the digitalSTROM heating controller app is installed.
186 * @param connectionManager (must not be null)
187 * @return true, if heating controller app is installed, otherwise false
189 public static boolean isHeatingControllerInstallated(ConnectionManager connectionManager) {
190 return connectionManager.getDigitalSTROMAPI().propertyTreeGetChildren(connectionManager.getSessionToken(),
191 GET_HEATING_HEATING_CONTROLLER_CHILDREN_PATH) != null;
195 * Returns all zone which have temperature controlled configured.
197 * @return all temperature controlled zones
199 public Collection<TemperatureControlStatus> getTemperatureControlStatusFromAllZones() {
200 return temperationControlStatus != null ? this.temperationControlStatus.values() : new LinkedList<>();
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}.
209 * @param temperatureControlStatusListener to register
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);
224 if (zoneTemperationControlListenerMap == null) {
225 zoneTemperationControlListenerMap = new HashMap<>();
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);
237 temperatureControlStatusListener.configChanged(tempConStat);
243 * Unregisters a {@link TemperatureControlStatusListener}, if it exist.
245 * @param temperatureControlStatusListener to unregister
247 public void unregisterTemperatureControlStatusListener(
248 TemperatureControlStatusListener temperatureControlStatusListener) {
249 if (temperatureControlStatusListener != null) {
250 if (temperatureControlStatusListener.getTemperationControlStatusListenrID()
251 .equals(TemperatureControlStatusListener.DISCOVERY)) {
252 this.discovery = null;
255 if (discovery != null && zoneTemperationControlListenerMap
256 .remove(temperatureControlStatusListener.getTemperationControlStatusListenrID()) != null) {
257 discovery.configChanged(temperationControlStatus
258 .get(temperatureControlStatusListener.getTemperationControlStatusListenrID()));
264 * Returns the {@link TemperatureControlStatus} for the given zone, if the temperature control is configured,
265 * otherwise it will be returned null.
267 * @param zoneID to check
268 * @return {@link TemperatureControlStatus} if the temperature control is configured, otherwise null
270 public TemperatureControlStatus checkAndGetTemperatureControlStatus(Integer zoneID) {
271 TemperatureControlStatus tempConStat = this.temperationControlStatus.get(zoneID);
272 if (tempConStat.isNotSetOff()) {
278 private boolean isEcho(Integer zoneID, SensorEnum sensorType, Float value) {
279 return echoBox.remove(zoneID + "-" + sensorType.getSensorType() + "-" + value);
282 private void addEcho(Integer zoneID, SensorEnum sensorType, Float value) {
283 echoBox.add(zoneID + "-" + sensorType.getSensorType() + "-" + value);
287 public void handleEvent(EventItem eventItem) {
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 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);
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());
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);
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);
343 } catch (NumberFormatException e) {
344 logger.debug("Unexpected missing or invalid number while handling event", e);
348 private void addTemperatureControlStatus(TemperatureControlStatus temperationControlStatus) {
349 if (temperationControlStatus.isNotSetOff()) {
350 if (this.temperationControlStatus == null) {
351 this.temperationControlStatus = new HashMap<>();
353 if (this.temperationControlStatus.get(temperationControlStatus.getZoneID()) == null && discovery != null) {
354 discovery.configChanged(temperationControlStatus);
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);
369 public List<String> getSupportedEvents() {
370 return SUPPORTED_EVENTS;
374 public boolean supportsEvent(String eventName) {
375 return SUPPORTED_EVENTS.contains(eventName);
379 public String getUID() {
380 return getClass().getSimpleName();
384 public void setEventListener(EventListener eventListener) {
385 eventListener.addEventHandler(this);
389 public void unsetEventListener(EventListener eventListener) {
390 eventListener.removeEventHandler(this);
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);
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);
419 * Returns true, if minimum one zone has temperature control configured.
421 * @return true, if minimum one zone has temperature control configured, otherwise false
423 public boolean isConfigured() {
428 * Returns the current heating water system state, if a {@link SystemStateChangeListener} is registered, otherwise
431 * @return the current heating water system state or null, if no {@link SystemStateChangeListener}
433 public String getHeatingWaterSystemState() {
434 return currentHeatingWaterSystemStage;
438 * Registers the given {@link SystemStateChangeListener}, which will be informed about heating system water state
441 * @param systemStateChangeListener to register
443 public void registerSystemStateChangeListener(SystemStateChangeListener systemStateChangeListener) {
444 if (eventListener != null) {
445 SUPPORTED_EVENTS.add(EventNames.STATE_CHANGED);
446 eventListener.addSubscribe(EventNames.STATE_CHANGED);
448 this.systemStateChangeListener = systemStateChangeListener;
452 * Unregisters a registered {@link SystemStateChangeListener}.
454 public void unregisterSystemStateChangeListener() {
455 this.systemStateChangeListener = null;