]> git.basschouten.com Git - openhab-addons.git/blob
9fba69a9bb4fe812245ac0450149acfe8bfe507b
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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      * a {@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             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);
296             }
297         }
298     }
299
300     @Override
301     public void removeInternalScene(String sceneID) {
302         this.internalSceneMap.remove(sceneID);
303     }
304
305     @Override
306     public InternalScene getInternalScene(String sceneID) {
307         return this.internalSceneMap.get(sceneID);
308     }
309
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);
326                         } else {
327                             sceneName = sceneName + " Group: " + groupID;
328                         }
329                     } else {
330                         if (structureManager.getZoneGroupName(zoneID, groupID) != null) {
331                             sceneName = "Zone: " + zoneID + " Group: "
332                                     + structureManager.getZoneGroupName(zoneID, groupID);
333                         } else {
334                             sceneName = "Zone: " + zoneID + " Group: " + groupID;
335                         }
336                     }
337                     sceneName = sceneName + " Scene: "
338                             + SceneEnum.getScene(sceneNumber).toString().toLowerCase().replace("_", " ");
339                 }
340                 intScene = new InternalScene(zoneID, groupID, sceneNumber, sceneName);
341             }
342             return intScene;
343         }
344         return null;
345     }
346
347     @Override
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);
352         } else {
353             device = this.structureManager.getDeviceByDSUID(dSID);
354             if (device != null) {
355                 device.internalCallScene(sceneID);
356             }
357         }
358     }
359
360     @Override
361     public void callDeviceScene(Device device, Short sceneID) {
362         if (device != null) {
363             callDeviceScene(device.getDSID().toString(), sceneID);
364         }
365     }
366
367     @Override
368     public void undoInternalScene(InternalScene scene) {
369         if (scene != null) {
370             undoInternalScene(scene.getID());
371         }
372     }
373
374     @Override
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();
380         } else {
381             intScene = createNewScene(sceneID);
382             if (intScene != null) {
383                 logger.debug("created new scene, deactivating it: {}", intScene.getSceneName());
384                 intScene.deactivateScene();
385             }
386         }
387     }
388
389     @Override
390     public void undoDeviceScene(String dSID) {
391         Device device = this.structureManager.getDeviceByDSID(new DSID(dSID));
392         if (device != null) {
393             device.internalUndoScene();
394         } else {
395             device = this.structureManager.getDeviceByDSUID(dSID);
396             if (device != null) {
397                 device.internalUndoScene();
398             }
399         }
400     }
401
402     @Override
403     public void undoDeviceScene(Device device) {
404         if (device != null) {
405             undoDeviceScene(device.getDSID().toString());
406         }
407     }
408
409     @Override
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);
418                 }
419             } else {
420                 InternalScene intScene = internalSceneMap.get(sceneListener.getSceneStatusListenerID());
421                 if (intScene != null) {
422                     intScene.registerSceneListener(sceneListener);
423                 } else {
424                     addInternalScene(createNewScene(id));
425                     registerSceneListener(sceneListener);
426                 }
427                 logger.debug("SceneStatusListener with id {} is registrated", sceneListener.getSceneStatusListenerID());
428             }
429         }
430     }
431
432     @Override
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");
439             } else {
440                 InternalScene intScene = this.internalSceneMap.get(sceneListener.getSceneStatusListenerID());
441                 if (intScene != null) {
442                     intScene.unregisterSceneListener();
443                 }
444                 logger.debug("SceneStatusListener with id {} is unregistrated",
445                         sceneListener.getSceneStatusListenerID());
446             }
447         }
448     }
449
450     @Override
451     public synchronized boolean scenesGenerated() {
452         return scenesGenerated;
453     }
454
455     @Override
456     public void generateScenes() {
457         stateChanged(ManagerStates.GENERATING_SCENES);
458         logger.debug("start generating scenes");
459         discovery.generateAllScenes(connectionManager, structureManager);
460     }
461
462     @Override
463     public void scenesGenerated(char[] scenesGenerated) {
464         if (String.valueOf(scenesGenerated).equals("1111")) {
465             this.scenesGenerated = true;
466             stateChanged(ManagerStates.RUNNING);
467         }
468         if (String.valueOf(scenesGenerated).contains("2")) {
469             String type = "nan";
470             switch (String.valueOf(scenesGenerated).indexOf("2")) {
471                 case 0:
472                     type = "namedScens";
473                     break;
474                 case 1:
475                     type = "appScenes";
476                     break;
477                 case 2:
478                     type = "zoneScenes";
479                     break;
480                 case 3:
481                     type = "reachableScenes";
482                     break;
483             }
484             logger.debug("Not all scenes are generated, try it again. Scene type {} is not generated.", type);
485             stateChanged(ManagerStates.RUNNING);
486         }
487     }
488
489     @Override
490     public boolean isDiscoveryRegistrated() {
491         return this.discovery != null;
492     }
493
494     private void stateChanged(ManagerStates state) {
495         this.state = state;
496         if (statusListener != null) {
497             statusListener.onStatusChanged(ManagerTypes.SCENE_MANAGER, state);
498         }
499     }
500
501     @Override
502     public ManagerTypes getManagerType() {
503         return ManagerTypes.SCENE_MANAGER;
504     }
505
506     @Override
507     public synchronized ManagerStates getManagerState() {
508         return state;
509     }
510
511     @Override
512     public List<InternalScene> getScenes() {
513         return this.internalSceneMap != null ? new LinkedList<>(this.internalSceneMap.values()) : null;
514     }
515
516     @Override
517     public void registerStatusListener(ManagerStatusListener statusListener) {
518         this.statusListener = statusListener;
519     }
520
521     @Override
522     public void unregisterStatusListener() {
523         this.statusListener = null;
524     }
525
526     @Override
527     public String getUID() {
528         return this.getClass().getSimpleName() + "-" + SUPPORTED_EVENTS.toString();
529     }
530
531     @Override
532     public List<String> getSupportedEvents() {
533         return SUPPORTED_EVENTS;
534     }
535
536     @Override
537     public boolean supportsEvent(String eventName) {
538         return SUPPORTED_EVENTS.contains(eventName);
539     }
540
541     @Override
542     public void setEventListener(EventListener eventListener) {
543         if (this.eventListener != null) {
544             this.eventListener.removeEventHandler(this);
545         }
546         this.eventListener = eventListener;
547     }
548
549     @Override
550     public void unsetEventListener(EventListener eventListener) {
551         if (this.eventListener != null) {
552             this.eventListener.removeEventHandler(this);
553         }
554         this.eventListener = null;
555     }
556 }