2 * Copyright (c) 2010-2023 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 * an {@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 if (oldScene != null) {
290 String oldSceneName = oldScene.getSceneName();
291 String newSceneName = intScene.getSceneName();
292 if ((oldSceneName.contains("Zone:") && oldSceneName.contains("Group:")
293 && oldSceneName.contains("Scene:"))
294 && !(newSceneName.contains("Zone:") && newSceneName.contains("Group:")
295 && newSceneName.contains("Scene:"))) {
296 oldScene.setSceneName(newSceneName);
297 this.discovery.sceneDiscoverd(oldScene);
304 public void removeInternalScene(String sceneID) {
305 this.internalSceneMap.remove(sceneID);
309 public InternalScene getInternalScene(String sceneID) {
310 return this.internalSceneMap.get(sceneID);
313 private InternalScene createNewScene(String sceneID) {
314 String[] sceneData = sceneID.split("-");
315 if (sceneData.length == 3) {
316 int zoneID = Integer.parseInt(sceneData[0]);
317 short groupID = Short.parseShort(sceneData[1]);
318 short sceneNumber = Short.parseShort(sceneData[2]);
319 String sceneName = null;
320 sceneName = connectionManager.getDigitalSTROMAPI().getSceneName(connectionManager.getSessionToken(), zoneID,
321 null, groupID, sceneNumber);
322 InternalScene intScene = null;
323 if (SceneEnum.getScene(sceneNumber) != null && structureManager.checkZoneGroupID(zoneID, groupID)) {
324 if (sceneName == null) {
325 if (structureManager.getZoneName(zoneID) != null) {
326 sceneName = "Zone: " + structureManager.getZoneName(zoneID);
327 if (structureManager.getZoneGroupName(zoneID, groupID) != null) {
328 sceneName = sceneName + " Group: " + structureManager.getZoneGroupName(zoneID, groupID);
330 sceneName = sceneName + " Group: " + groupID;
333 if (structureManager.getZoneGroupName(zoneID, groupID) != null) {
334 sceneName = "Zone: " + zoneID + " Group: "
335 + structureManager.getZoneGroupName(zoneID, groupID);
337 sceneName = "Zone: " + zoneID + " Group: " + groupID;
340 sceneName = sceneName + " Scene: "
341 + SceneEnum.getScene(sceneNumber).toString().toLowerCase().replace("_", " ");
343 intScene = new InternalScene(zoneID, groupID, sceneNumber, sceneName);
351 public void callDeviceScene(String dSID, Short sceneID) {
352 Device device = this.structureManager.getDeviceByDSID(new DSID(dSID));
353 if (device != null) {
354 device.internalCallScene(sceneID);
356 device = this.structureManager.getDeviceByDSUID(dSID);
357 if (device != null) {
358 device.internalCallScene(sceneID);
364 public void callDeviceScene(Device device, Short sceneID) {
365 if (device != null) {
366 callDeviceScene(device.getDSID().toString(), sceneID);
371 public void undoInternalScene(InternalScene scene) {
373 undoInternalScene(scene.getID());
378 public void undoInternalScene(String sceneID) {
379 InternalScene intScene = this.internalSceneMap.get(sceneID);
380 if (intScene != null) {
381 logger.debug("deactivating existing scene {}", intScene.getSceneName());
382 intScene.deactivateScene();
384 intScene = createNewScene(sceneID);
385 if (intScene != null) {
386 logger.debug("created new scene, deactivating it: {}", intScene.getSceneName());
387 intScene.deactivateScene();
393 public void undoDeviceScene(String dSID) {
394 Device device = this.structureManager.getDeviceByDSID(new DSID(dSID));
395 if (device != null) {
396 device.internalUndoScene();
398 device = this.structureManager.getDeviceByDSUID(dSID);
399 if (device != null) {
400 device.internalUndoScene();
406 public void undoDeviceScene(Device device) {
407 if (device != null) {
408 undoDeviceScene(device.getDSID().toString());
413 public void registerSceneListener(SceneStatusListener sceneListener) {
414 if (sceneListener != null) {
415 String id = sceneListener.getSceneStatusListenerID();
416 if (id.equals(SceneStatusListener.SCENE_DISCOVERY)) {
417 discovery.registerSceneDiscovery(sceneListener);
418 logger.debug("Scene-Discovery registrated");
419 for (InternalScene scene : internalSceneMap.values()) {
420 discovery.sceneDiscoverd(scene);
423 InternalScene intScene = internalSceneMap.get(sceneListener.getSceneStatusListenerID());
424 if (intScene != null) {
425 intScene.registerSceneListener(sceneListener);
427 addInternalScene(createNewScene(id));
428 registerSceneListener(sceneListener);
430 logger.debug("SceneStatusListener with id {} is registrated", sceneListener.getSceneStatusListenerID());
436 public void unregisterSceneListener(SceneStatusListener sceneListener) {
437 if (sceneListener != null) {
438 String id = sceneListener.getSceneStatusListenerID();
439 if (id.equals(SceneStatusListener.SCENE_DISCOVERY)) {
440 this.discovery.unRegisterDiscovery();
441 logger.debug("Scene-Discovery unregistrated");
443 InternalScene intScene = this.internalSceneMap.get(sceneListener.getSceneStatusListenerID());
444 if (intScene != null) {
445 intScene.unregisterSceneListener();
447 logger.debug("SceneStatusListener with id {} is unregistrated",
448 sceneListener.getSceneStatusListenerID());
454 public synchronized boolean scenesGenerated() {
455 return scenesGenerated;
459 public void generateScenes() {
460 stateChanged(ManagerStates.GENERATING_SCENES);
461 logger.debug("start generating scenes");
462 discovery.generateAllScenes(connectionManager, structureManager);
466 public void scenesGenerated(char[] scenesGenerated) {
467 if ("1111".equals(String.valueOf(scenesGenerated))) {
468 this.scenesGenerated = true;
469 stateChanged(ManagerStates.RUNNING);
471 if (String.valueOf(scenesGenerated).contains("2")) {
473 switch (String.valueOf(scenesGenerated).indexOf("2")) {
484 type = "reachableScenes";
487 logger.debug("Not all scenes are generated, try it again. Scene type {} is not generated.", type);
488 stateChanged(ManagerStates.RUNNING);
493 public boolean isDiscoveryRegistrated() {
494 return this.discovery != null;
497 private void stateChanged(ManagerStates state) {
499 if (statusListener != null) {
500 statusListener.onStatusChanged(ManagerTypes.SCENE_MANAGER, state);
505 public ManagerTypes getManagerType() {
506 return ManagerTypes.SCENE_MANAGER;
510 public synchronized ManagerStates getManagerState() {
515 public List<InternalScene> getScenes() {
516 return this.internalSceneMap != null ? new LinkedList<>(this.internalSceneMap.values()) : null;
520 public void registerStatusListener(ManagerStatusListener statusListener) {
521 this.statusListener = statusListener;
525 public void unregisterStatusListener() {
526 this.statusListener = null;
530 public String getUID() {
531 return this.getClass().getSimpleName() + "-" + SUPPORTED_EVENTS.toString();
535 public List<String> getSupportedEvents() {
536 return SUPPORTED_EVENTS;
540 public boolean supportsEvent(String eventName) {
541 return SUPPORTED_EVENTS.contains(eventName);
545 public void setEventListener(EventListener eventListener) {
546 if (this.eventListener != null) {
547 this.eventListener.removeEventHandler(this);
549 this.eventListener = eventListener;
553 public void unsetEventListener(EventListener eventListener) {
554 if (this.eventListener != null) {
555 this.eventListener.removeEventHandler(this);
557 this.eventListener = null;