]> git.basschouten.com Git - openhab-addons.git/blob
7f101e5df000667d212301c5fcb3673dda7d2a95
[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.Collections;
17 import java.util.HashMap;
18 import java.util.LinkedList;
19 import java.util.List;
20 import java.util.Map;
21
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;
40
41 /**
42  * The {@link SceneManagerImpl} is the implementation of the {@link SceneManager}.
43  *
44  * @author Michael Ochel - Initial contribution
45  * @author Matthias Siegele - Initial contribution
46  *
47  */
48 public class SceneManagerImpl implements SceneManager {
49
50     /**
51      * Contains all supported event-types.
52      */
53     public static final List<String> SUPPORTED_EVENTS = Arrays.asList(EventNames.CALL_SCENE, EventNames.UNDO_SCENE);
54
55     private final Logger logger = LoggerFactory.getLogger(SceneManagerImpl.class);
56
57     private final List<String> echoBox = Collections.synchronizedList(new LinkedList<>());
58     private final Map<String, InternalScene> internalSceneMap = Collections.synchronizedMap(new HashMap<>());
59
60     private EventListener eventListener;
61     private final StructureManager structureManager;
62     private final ConnectionManager connectionManager;
63     private final SceneDiscovery discovery;
64     private ManagerStatusListener statusListener;
65
66     private ManagerStates state = ManagerStates.STOPPED;
67     private boolean scenesGenerated = false;
68
69     /**
70      * Creates a new {@link SceneManagerImpl} through the given managers.
71      *
72      * @param connectionManager (must not be null)
73      * @param structureManager (must not be null)
74      */
75     public SceneManagerImpl(ConnectionManager connectionManager, StructureManager structureManager) {
76         this.structureManager = structureManager;
77         this.connectionManager = connectionManager;
78         this.discovery = new SceneDiscovery(this);
79     }
80
81     /**
82      * Same constructor like {@link #SceneManagerImpl(ConnectionManager, StructureManager)}, but a
83      * {@link ManagerStatusListener} can be set, too.
84      *
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)
89      */
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;
96     }
97
98     /**
99      * Same constructor like {@link #SceneManagerImpl(ConnectionManager, StructureManager, ManagerStatusListener)}, but
100      * an {@link EventListener} can be set, too.
101      *
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)
107      */
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;
115     }
116
117     @Override
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);
123         } else {
124             logger.debug("EventListener is set, add this SceneManager as EventHandler");
125             eventListener.addEventHandler(this);
126         }
127         eventListener.start();
128         logger.debug("start SceneManager");
129         stateChanged(ManagerStates.RUNNING);
130     }
131
132     @Override
133     public void stop() {
134         logger.debug("stop SceneManager");
135         if (eventListener != null) {
136             eventListener.removeEventHandler(this);
137         }
138         this.discovery.stop();
139         this.stateChanged(ManagerStates.STOPPED);
140     }
141
142     @Override
143     public void handleEvent(EventItem eventItem) {
144         if (eventItem == null) {
145             return;
146         }
147         boolean isCallScene = true;
148         String isCallStr = eventItem.getName();
149         if (isCallStr != null) {
150             isCallScene = isCallStr.equals(EventNames.CALL_SCENE);
151         }
152
153         boolean isDeviceCall = false;
154         String deviceCallStr = eventItem.getSource().get(EventResponseEnum.IS_DEVICE);
155         if (deviceCallStr != null) {
156             isDeviceCall = Boolean.parseBoolean(deviceCallStr);
157         }
158
159         if (isDeviceCall) {
160             String dsidStr = null;
161             dsidStr = eventItem.getSource().get(EventResponseEnum.DSID);
162             short sceneId = -1;
163             String sceneStr = eventItem.getProperties().get(EventResponseEnum.SCENEID);
164             if (sceneStr != null) {
165                 try {
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);
169                 }
170             }
171
172             if (!isEcho(dsidStr, sceneId)) {
173                 logger.debug("{} event for device: {}", eventItem.getName(), dsidStr);
174                 if (isCallScene) {
175                     this.callDeviceScene(dsidStr, sceneId);
176                 } else {
177                     this.undoDeviceScene(dsidStr);
178                 }
179             }
180         } else {
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);
185
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,
190                             sceneIDStr);
191                     if (isCallScene) {
192                         this.callInternalScene(intSceneID);
193                     } else {
194                         this.undoInternalScene(intSceneID);
195                     }
196                 }
197             }
198         }
199     }
200
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);
207         return isEcho(echo);
208     }
209
210     private boolean isEcho(String echoID) {
211         if (echoBox.contains(echoID)) {
212             echoBox.remove(echoID);
213             return true;
214         }
215         return false;
216     }
217
218     // ... we want to ignore own 'command-echos'
219     @Override
220     public void addEcho(String dsid, short sceneId) {
221         addEcho(dsid + "-" + sceneId);
222     }
223
224     // ... we want to ignore own 'command-echos'
225     @Override
226     public void addEcho(String internalSceneID) {
227         echoBox.add(internalSceneID);
228     }
229
230     @Override
231     public void callInternalScene(InternalScene scene) {
232         InternalScene intScene = this.internalSceneMap.get(scene.getID());
233         if (intScene != null) {
234             intScene.activateScene();
235         } else {
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();
242             }
243         }
244     }
245
246     @Override
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();
251         } else {
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();
257             }
258         }
259     }
260
261     @Override
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();
267         } else {
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();
273             }
274         }
275     }
276
277     @Override
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);
285
286             }
287         } else {
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);
298                 }
299             }
300         }
301     }
302
303     @Override
304     public void removeInternalScene(String sceneID) {
305         this.internalSceneMap.remove(sceneID);
306     }
307
308     @Override
309     public InternalScene getInternalScene(String sceneID) {
310         return this.internalSceneMap.get(sceneID);
311     }
312
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);
329                         } else {
330                             sceneName = sceneName + " Group: " + groupID;
331                         }
332                     } else {
333                         if (structureManager.getZoneGroupName(zoneID, groupID) != null) {
334                             sceneName = "Zone: " + zoneID + " Group: "
335                                     + structureManager.getZoneGroupName(zoneID, groupID);
336                         } else {
337                             sceneName = "Zone: " + zoneID + " Group: " + groupID;
338                         }
339                     }
340                     sceneName = sceneName + " Scene: "
341                             + SceneEnum.getScene(sceneNumber).toString().toLowerCase().replace("_", " ");
342                 }
343                 intScene = new InternalScene(zoneID, groupID, sceneNumber, sceneName);
344             }
345             return intScene;
346         }
347         return null;
348     }
349
350     @Override
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);
355         } else {
356             device = this.structureManager.getDeviceByDSUID(dSID);
357             if (device != null) {
358                 device.internalCallScene(sceneID);
359             }
360         }
361     }
362
363     @Override
364     public void callDeviceScene(Device device, Short sceneID) {
365         if (device != null) {
366             callDeviceScene(device.getDSID().toString(), sceneID);
367         }
368     }
369
370     @Override
371     public void undoInternalScene(InternalScene scene) {
372         if (scene != null) {
373             undoInternalScene(scene.getID());
374         }
375     }
376
377     @Override
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();
383         } else {
384             intScene = createNewScene(sceneID);
385             if (intScene != null) {
386                 logger.debug("created new scene, deactivating it: {}", intScene.getSceneName());
387                 intScene.deactivateScene();
388             }
389         }
390     }
391
392     @Override
393     public void undoDeviceScene(String dSID) {
394         Device device = this.structureManager.getDeviceByDSID(new DSID(dSID));
395         if (device != null) {
396             device.internalUndoScene();
397         } else {
398             device = this.structureManager.getDeviceByDSUID(dSID);
399             if (device != null) {
400                 device.internalUndoScene();
401             }
402         }
403     }
404
405     @Override
406     public void undoDeviceScene(Device device) {
407         if (device != null) {
408             undoDeviceScene(device.getDSID().toString());
409         }
410     }
411
412     @Override
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);
421                 }
422             } else {
423                 InternalScene intScene = internalSceneMap.get(sceneListener.getSceneStatusListenerID());
424                 if (intScene != null) {
425                     intScene.registerSceneListener(sceneListener);
426                 } else {
427                     addInternalScene(createNewScene(id));
428                     registerSceneListener(sceneListener);
429                 }
430                 logger.debug("SceneStatusListener with id {} is registrated", sceneListener.getSceneStatusListenerID());
431             }
432         }
433     }
434
435     @Override
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");
442             } else {
443                 InternalScene intScene = this.internalSceneMap.get(sceneListener.getSceneStatusListenerID());
444                 if (intScene != null) {
445                     intScene.unregisterSceneListener();
446                 }
447                 logger.debug("SceneStatusListener with id {} is unregistrated",
448                         sceneListener.getSceneStatusListenerID());
449             }
450         }
451     }
452
453     @Override
454     public synchronized boolean scenesGenerated() {
455         return scenesGenerated;
456     }
457
458     @Override
459     public void generateScenes() {
460         stateChanged(ManagerStates.GENERATING_SCENES);
461         logger.debug("start generating scenes");
462         discovery.generateAllScenes(connectionManager, structureManager);
463     }
464
465     @Override
466     public void scenesGenerated(char[] scenesGenerated) {
467         if (String.valueOf(scenesGenerated).equals("1111")) {
468             this.scenesGenerated = true;
469             stateChanged(ManagerStates.RUNNING);
470         }
471         if (String.valueOf(scenesGenerated).contains("2")) {
472             String type = "nan";
473             switch (String.valueOf(scenesGenerated).indexOf("2")) {
474                 case 0:
475                     type = "namedScens";
476                     break;
477                 case 1:
478                     type = "appScenes";
479                     break;
480                 case 2:
481                     type = "zoneScenes";
482                     break;
483                 case 3:
484                     type = "reachableScenes";
485                     break;
486             }
487             logger.debug("Not all scenes are generated, try it again. Scene type {} is not generated.", type);
488             stateChanged(ManagerStates.RUNNING);
489         }
490     }
491
492     @Override
493     public boolean isDiscoveryRegistrated() {
494         return this.discovery != null;
495     }
496
497     private void stateChanged(ManagerStates state) {
498         this.state = state;
499         if (statusListener != null) {
500             statusListener.onStatusChanged(ManagerTypes.SCENE_MANAGER, state);
501         }
502     }
503
504     @Override
505     public ManagerTypes getManagerType() {
506         return ManagerTypes.SCENE_MANAGER;
507     }
508
509     @Override
510     public synchronized ManagerStates getManagerState() {
511         return state;
512     }
513
514     @Override
515     public List<InternalScene> getScenes() {
516         return this.internalSceneMap != null ? new LinkedList<>(this.internalSceneMap.values()) : null;
517     }
518
519     @Override
520     public void registerStatusListener(ManagerStatusListener statusListener) {
521         this.statusListener = statusListener;
522     }
523
524     @Override
525     public void unregisterStatusListener() {
526         this.statusListener = null;
527     }
528
529     @Override
530     public String getUID() {
531         return this.getClass().getSimpleName() + "-" + SUPPORTED_EVENTS.toString();
532     }
533
534     @Override
535     public List<String> getSupportedEvents() {
536         return SUPPORTED_EVENTS;
537     }
538
539     @Override
540     public boolean supportsEvent(String eventName) {
541         return SUPPORTED_EVENTS.contains(eventName);
542     }
543
544     @Override
545     public void setEventListener(EventListener eventListener) {
546         if (this.eventListener != null) {
547             this.eventListener.removeEventHandler(this);
548         }
549         this.eventListener = eventListener;
550     }
551
552     @Override
553     public void unsetEventListener(EventListener eventListener) {
554         if (this.eventListener != null) {
555             this.eventListener.removeEventHandler(this);
556         }
557         this.eventListener = null;
558     }
559 }