2 * Copyright (c) 2010-2021 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.Collections;
17 import java.util.HashMap;
18 import java.util.LinkedList;
19 import java.util.List;
22 import org.openhab.binding.digitalstrom.internal.lib.event.EventListener;
23 import org.openhab.binding.digitalstrom.internal.lib.event.constants.EventNames;
24 import org.openhab.binding.digitalstrom.internal.lib.event.constants.EventResponseEnum;
25 import org.openhab.binding.digitalstrom.internal.lib.event.types.EventItem;
26 import org.openhab.binding.digitalstrom.internal.lib.listener.ManagerStatusListener;
27 import org.openhab.binding.digitalstrom.internal.lib.listener.SceneStatusListener;
28 import org.openhab.binding.digitalstrom.internal.lib.listener.stateenums.ManagerStates;
29 import org.openhab.binding.digitalstrom.internal.lib.listener.stateenums.ManagerTypes;
30 import org.openhab.binding.digitalstrom.internal.lib.manager.ConnectionManager;
31 import org.openhab.binding.digitalstrom.internal.lib.manager.SceneManager;
32 import org.openhab.binding.digitalstrom.internal.lib.manager.StructureManager;
33 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.Device;
34 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.impl.DSID;
35 import org.openhab.binding.digitalstrom.internal.lib.structure.scene.InternalScene;
36 import org.openhab.binding.digitalstrom.internal.lib.structure.scene.SceneDiscovery;
37 import org.openhab.binding.digitalstrom.internal.lib.structure.scene.constants.SceneEnum;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
42 * The {@link SceneManagerImpl} is the implementation of the {@link SceneManager}.
44 * @author Michael Ochel - Initial contribution
45 * @author Matthias Siegele - Initial contribution
48 public class SceneManagerImpl implements SceneManager {
51 * Contains all supported event-types.
53 public static final List<String> SUPPORTED_EVENTS = Arrays.asList(EventNames.CALL_SCENE, EventNames.UNDO_SCENE);
55 private final Logger logger = LoggerFactory.getLogger(SceneManagerImpl.class);
57 private final List<String> echoBox = Collections.synchronizedList(new LinkedList<>());
58 private final Map<String, InternalScene> internalSceneMap = Collections.synchronizedMap(new HashMap<>());
60 private EventListener eventListener;
61 private final StructureManager structureManager;
62 private final ConnectionManager connectionManager;
63 private final SceneDiscovery discovery;
64 private ManagerStatusListener statusListener;
66 private ManagerStates state = ManagerStates.STOPPED;
67 private boolean scenesGenerated = false;
70 * Creates a new {@link SceneManagerImpl} through the given managers.
72 * @param connectionManager (must not be null)
73 * @param structureManager (must not be null)
75 public SceneManagerImpl(ConnectionManager connectionManager, StructureManager structureManager) {
76 this.structureManager = structureManager;
77 this.connectionManager = connectionManager;
78 this.discovery = new SceneDiscovery(this);
82 * Same constructor like {@link #SceneManagerImpl(ConnectionManager, StructureManager)}, but a
83 * {@link ManagerStatusListener} can be set, too.
85 * @param connectionManager (must not be null)
86 * @param structureManager (must not be null)
87 * @param statusListener (can be null)
88 * @see #SceneManagerImpl(ConnectionManager, StructureManager)
90 public SceneManagerImpl(ConnectionManager connectionManager, StructureManager structureManager,
91 ManagerStatusListener statusListener) {
92 this.structureManager = structureManager;
93 this.connectionManager = connectionManager;
94 this.discovery = new SceneDiscovery(this);
95 this.statusListener = statusListener;
99 * Same constructor like {@link #SceneManagerImpl(ConnectionManager, StructureManager, ManagerStatusListener)}, but
100 * a {@link EventListener} can be set, too.
102 * @param connectionManager (must not be null)
103 * @param structureManager (must not be null)
104 * @param statusListener (can be null)
105 * @param eventListener (can be null)
106 * @see #SceneManagerImpl(ConnectionManager, StructureManager, ManagerStatusListener)
108 public SceneManagerImpl(ConnectionManager connectionManager, StructureManager structureManager,
109 ManagerStatusListener statusListener, EventListener eventListener) {
110 this.structureManager = structureManager;
111 this.connectionManager = connectionManager;
112 this.discovery = new SceneDiscovery(this);
113 this.statusListener = statusListener;
114 this.eventListener = eventListener;
118 public void start() {
119 logger.debug("start SceneManager");
120 if (eventListener == null) {
121 logger.debug("no EventListener is set, create a new EventListener");
122 eventListener = new EventListener(connectionManager, this);
124 logger.debug("EventListener is set, add this SceneManager as EventHandler");
125 eventListener.addEventHandler(this);
127 eventListener.start();
128 logger.debug("start SceneManager");
129 stateChanged(ManagerStates.RUNNING);
134 logger.debug("stop SceneManager");
135 if (eventListener != null) {
136 eventListener.removeEventHandler(this);
138 this.discovery.stop();
139 this.stateChanged(ManagerStates.STOPPED);
143 public void handleEvent(EventItem eventItem) {
144 if (eventItem == null) {
147 boolean isCallScene = true;
148 String isCallStr = eventItem.getName();
149 if (isCallStr != null) {
150 isCallScene = isCallStr.equals(EventNames.CALL_SCENE);
153 boolean isDeviceCall = false;
154 String deviceCallStr = eventItem.getSource().get(EventResponseEnum.IS_DEVICE);
155 if (deviceCallStr != null) {
156 isDeviceCall = Boolean.parseBoolean(deviceCallStr);
160 String dsidStr = null;
161 dsidStr = eventItem.getSource().get(EventResponseEnum.DSID);
163 String sceneStr = eventItem.getProperties().get(EventResponseEnum.SCENEID);
164 if (sceneStr != null) {
166 sceneId = Short.parseShort(sceneStr);
167 } catch (java.lang.NumberFormatException e) {
168 logger.error("An exception occurred, while handling event at parsing sceneID: {}", sceneStr, e);
172 if (!isEcho(dsidStr, sceneId)) {
173 logger.debug("{} event for device: {}", eventItem.getName(), dsidStr);
175 this.callDeviceScene(dsidStr, sceneId);
177 this.undoDeviceScene(dsidStr);
181 String intSceneID = null;
182 String zoneIDStr = eventItem.getSource().get(EventResponseEnum.ZONEID);
183 String groupIDStr = eventItem.getSource().get(EventResponseEnum.GROUPID);
184 String sceneIDStr = eventItem.getProperties().get(EventResponseEnum.SCENEID);
186 if (zoneIDStr != null && sceneIDStr != null && groupIDStr != null) {
187 intSceneID = zoneIDStr + "-" + groupIDStr + "-" + sceneIDStr;
188 if (!isEcho(intSceneID)) {
189 logger.debug("{} event for scene: {}-{}-{}", eventItem.getName(), zoneIDStr, groupIDStr,
192 this.callInternalScene(intSceneID);
194 this.undoInternalScene(intSceneID);
201 private boolean isEcho(String dsid, short sceneId) {
202 // sometimes the dS-event has a dSUID saved in the dSID
203 String echo = (structureManager.getDeviceByDSUID(dsid) != null
204 ? structureManager.getDeviceByDSUID(dsid).getDSID().getValue()
205 : dsid) + "-" + sceneId;
206 logger.debug("An echo scene event was detected: {}", echo);
210 private boolean isEcho(String echoID) {
211 if (echoBox.contains(echoID)) {
212 echoBox.remove(echoID);
218 // ... we want to ignore own 'command-echos'
220 public void addEcho(String dsid, short sceneId) {
221 addEcho(dsid + "-" + sceneId);
224 // ... we want to ignore own 'command-echos'
226 public void addEcho(String internalSceneID) {
227 echoBox.add(internalSceneID);
231 public void callInternalScene(InternalScene scene) {
232 InternalScene intScene = this.internalSceneMap.get(scene.getID());
233 if (intScene != null) {
234 intScene.activateScene();
236 if (SceneEnum.getScene(scene.getSceneID()) != null
237 && structureManager.checkZoneGroupID(scene.getZoneID(), scene.getGroupID())) {
238 scene.addReferenceDevices(this.structureManager.getReferenceDeviceListFromZoneXGroupX(scene.getZoneID(),
239 scene.getGroupID()));
240 this.internalSceneMap.put(scene.getID(), scene);
241 scene.activateScene();
247 public void callInternalSceneWithoutDiscovery(Integer zoneID, Short groupID, Short sceneID) {
248 InternalScene intScene = this.internalSceneMap.get(zoneID + "-" + groupID + "-" + sceneID);
249 if (intScene != null) {
250 intScene.activateScene();
252 InternalScene scene = new InternalScene(zoneID, groupID, sceneID, null);
253 if (structureManager.checkZoneGroupID(scene.getZoneID(), scene.getGroupID())) {
254 scene.addReferenceDevices(this.structureManager.getReferenceDeviceListFromZoneXGroupX(scene.getZoneID(),
255 scene.getGroupID()));
256 scene.activateScene();
262 public void callInternalScene(String sceneID) {
263 InternalScene intScene = this.internalSceneMap.get(sceneID);
264 if (intScene != null) {
265 logger.debug("activating existing scene {}", intScene.getSceneName());
266 intScene.activateScene();
268 intScene = createNewScene(sceneID);
269 if (intScene != null) {
270 logger.debug("created new scene, activating it: {}", intScene.getSceneName());
271 discovery.sceneDiscoverd(intScene);
272 intScene.activateScene();
278 public void addInternalScene(InternalScene intScene) {
279 if (!this.internalSceneMap.containsKey(intScene.getID())) {
280 if (SceneEnum.getScene(intScene.getSceneID()) != null
281 && structureManager.checkZoneGroupID(intScene.getZoneID(), intScene.getGroupID())) {
282 intScene.addReferenceDevices(this.structureManager
283 .getReferenceDeviceListFromZoneXGroupX(intScene.getZoneID(), intScene.getGroupID()));
284 this.internalSceneMap.put(intScene.getID(), intScene);
288 InternalScene oldScene = this.internalSceneMap.get(intScene.getID());
289 String oldSceneName = this.internalSceneMap.get(intScene.getID()).getSceneName();
290 String newSceneName = intScene.getSceneName();
291 if ((oldSceneName.contains("Zone:") && oldSceneName.contains("Group:") && oldSceneName.contains("Scene:"))
292 && !(newSceneName.contains("Zone:") && newSceneName.contains("Group:")
293 && newSceneName.contains("Scene:"))) {
294 oldScene.setSceneName(newSceneName);
295 this.discovery.sceneDiscoverd(oldScene);
301 public void removeInternalScene(String sceneID) {
302 this.internalSceneMap.remove(sceneID);
306 public InternalScene getInternalScene(String sceneID) {
307 return this.internalSceneMap.get(sceneID);
310 private InternalScene createNewScene(String sceneID) {
311 String[] sceneData = sceneID.split("-");
312 if (sceneData.length == 3) {
313 int zoneID = Integer.parseInt(sceneData[0]);
314 short groupID = Short.parseShort(sceneData[1]);
315 short sceneNumber = Short.parseShort(sceneData[2]);
316 String sceneName = null;
317 sceneName = connectionManager.getDigitalSTROMAPI().getSceneName(connectionManager.getSessionToken(), zoneID,
318 null, groupID, sceneNumber);
319 InternalScene intScene = null;
320 if (SceneEnum.getScene(sceneNumber) != null && structureManager.checkZoneGroupID(zoneID, groupID)) {
321 if (sceneName == null) {
322 if (structureManager.getZoneName(zoneID) != null) {
323 sceneName = "Zone: " + structureManager.getZoneName(zoneID);
324 if (structureManager.getZoneGroupName(zoneID, groupID) != null) {
325 sceneName = sceneName + " Group: " + structureManager.getZoneGroupName(zoneID, groupID);
327 sceneName = sceneName + " Group: " + groupID;
330 if (structureManager.getZoneGroupName(zoneID, groupID) != null) {
331 sceneName = "Zone: " + zoneID + " Group: "
332 + structureManager.getZoneGroupName(zoneID, groupID);
334 sceneName = "Zone: " + zoneID + " Group: " + groupID;
337 sceneName = sceneName + " Scene: "
338 + SceneEnum.getScene(sceneNumber).toString().toLowerCase().replace("_", " ");
340 intScene = new InternalScene(zoneID, groupID, sceneNumber, sceneName);
348 public void callDeviceScene(String dSID, Short sceneID) {
349 Device device = this.structureManager.getDeviceByDSID(new DSID(dSID));
350 if (device != null) {
351 device.internalCallScene(sceneID);
353 device = this.structureManager.getDeviceByDSUID(dSID);
354 if (device != null) {
355 device.internalCallScene(sceneID);
361 public void callDeviceScene(Device device, Short sceneID) {
362 if (device != null) {
363 callDeviceScene(device.getDSID().toString(), sceneID);
368 public void undoInternalScene(InternalScene scene) {
370 undoInternalScene(scene.getID());
375 public void undoInternalScene(String sceneID) {
376 InternalScene intScene = this.internalSceneMap.get(sceneID);
377 if (intScene != null) {
378 logger.debug("deactivating existing scene {}", intScene.getSceneName());
379 intScene.deactivateScene();
381 intScene = createNewScene(sceneID);
382 if (intScene != null) {
383 logger.debug("created new scene, deactivating it: {}", intScene.getSceneName());
384 intScene.deactivateScene();
390 public void undoDeviceScene(String dSID) {
391 Device device = this.structureManager.getDeviceByDSID(new DSID(dSID));
392 if (device != null) {
393 device.internalUndoScene();
395 device = this.structureManager.getDeviceByDSUID(dSID);
396 if (device != null) {
397 device.internalUndoScene();
403 public void undoDeviceScene(Device device) {
404 if (device != null) {
405 undoDeviceScene(device.getDSID().toString());
410 public void registerSceneListener(SceneStatusListener sceneListener) {
411 if (sceneListener != null) {
412 String id = sceneListener.getSceneStatusListenerID();
413 if (id.equals(SceneStatusListener.SCENE_DISCOVERY)) {
414 discovery.registerSceneDiscovery(sceneListener);
415 logger.debug("Scene-Discovery registrated");
416 for (InternalScene scene : internalSceneMap.values()) {
417 discovery.sceneDiscoverd(scene);
420 InternalScene intScene = internalSceneMap.get(sceneListener.getSceneStatusListenerID());
421 if (intScene != null) {
422 intScene.registerSceneListener(sceneListener);
424 addInternalScene(createNewScene(id));
425 registerSceneListener(sceneListener);
427 logger.debug("SceneStatusListener with id {} is registrated", sceneListener.getSceneStatusListenerID());
433 public void unregisterSceneListener(SceneStatusListener sceneListener) {
434 if (sceneListener != null) {
435 String id = sceneListener.getSceneStatusListenerID();
436 if (id.equals(SceneStatusListener.SCENE_DISCOVERY)) {
437 this.discovery.unRegisterDiscovery();
438 logger.debug("Scene-Discovery unregistrated");
440 InternalScene intScene = this.internalSceneMap.get(sceneListener.getSceneStatusListenerID());
441 if (intScene != null) {
442 intScene.unregisterSceneListener();
444 logger.debug("SceneStatusListener with id {} is unregistrated",
445 sceneListener.getSceneStatusListenerID());
451 public synchronized boolean scenesGenerated() {
452 return scenesGenerated;
456 public void generateScenes() {
457 stateChanged(ManagerStates.GENERATING_SCENES);
458 logger.debug("start generating scenes");
459 discovery.generateAllScenes(connectionManager, structureManager);
463 public void scenesGenerated(char[] scenesGenerated) {
464 if (String.valueOf(scenesGenerated).equals("1111")) {
465 this.scenesGenerated = true;
466 stateChanged(ManagerStates.RUNNING);
468 if (String.valueOf(scenesGenerated).contains("2")) {
470 switch (String.valueOf(scenesGenerated).indexOf("2")) {
481 type = "reachableScenes";
484 logger.debug("Not all scenes are generated, try it again. Scene type {} is not generated.", type);
485 stateChanged(ManagerStates.RUNNING);
490 public boolean isDiscoveryRegistrated() {
491 return this.discovery != null;
494 private void stateChanged(ManagerStates state) {
496 if (statusListener != null) {
497 statusListener.onStatusChanged(ManagerTypes.SCENE_MANAGER, state);
502 public ManagerTypes getManagerType() {
503 return ManagerTypes.SCENE_MANAGER;
507 public synchronized ManagerStates getManagerState() {
512 public List<InternalScene> getScenes() {
513 return this.internalSceneMap != null ? new LinkedList<>(this.internalSceneMap.values()) : null;
517 public void registerStatusListener(ManagerStatusListener statusListener) {
518 this.statusListener = statusListener;
522 public void unregisterStatusListener() {
523 this.statusListener = null;
527 public String getUID() {
528 return this.getClass().getSimpleName() + "-" + SUPPORTED_EVENTS.toString();
532 public List<String> getSupportedEvents() {
533 return SUPPORTED_EVENTS;
537 public boolean supportsEvent(String eventName) {
538 return SUPPORTED_EVENTS.contains(eventName);
542 public void setEventListener(EventListener eventListener) {
543 if (this.eventListener != null) {
544 this.eventListener.removeEventHandler(this);
546 this.eventListener = eventListener;
550 public void unsetEventListener(EventListener eventListener) {
551 if (this.eventListener != null) {
552 this.eventListener.removeEventHandler(this);
554 this.eventListener = null;