]> git.basschouten.com Git - openhab-addons.git/blob
856d1b5f51a2a398152a525af2631ae196fbc431
[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.structure.scene;
14
15 import java.util.HashMap;
16 import java.util.Iterator;
17 import java.util.LinkedList;
18 import java.util.List;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
21
22 import org.openhab.binding.digitalstrom.internal.lib.config.Config;
23 import org.openhab.binding.digitalstrom.internal.lib.listener.SceneStatusListener;
24 import org.openhab.binding.digitalstrom.internal.lib.manager.ConnectionManager;
25 import org.openhab.binding.digitalstrom.internal.lib.manager.SceneManager;
26 import org.openhab.binding.digitalstrom.internal.lib.manager.StructureManager;
27 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.constants.JSONApiResponseKeysEnum;
28 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.impl.JSONResponseHandler;
29 import org.openhab.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.ApplicationGroup;
30 import org.openhab.binding.digitalstrom.internal.lib.structure.scene.constants.ApartmentSceneEnum;
31 import org.openhab.binding.digitalstrom.internal.lib.structure.scene.constants.SceneEnum;
32 import org.openhab.binding.digitalstrom.internal.lib.structure.scene.constants.ZoneSceneEnum;
33 import org.openhab.core.common.ThreadPoolManager;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36
37 import com.google.gson.JsonArray;
38 import com.google.gson.JsonObject;
39
40 /**
41  * The {@link SceneDiscovery} can read out various digitalSTROM-Scene types and generates a list of theirs or manages it
42  * by the {@link SceneManager}.
43  *
44  * @author Michael Ochel - Initial contribution
45  * @author Matthias Siegele - Initial contribution
46  */
47 public class SceneDiscovery {
48
49     private final Logger logger = LoggerFactory.getLogger(SceneDiscovery.class);
50     // fields: 0 = namedScenes, 1 = apartmentScenes, 2 = zoneScenes, 3 = reachableScenes
51     private final char[] scenesGenerated = "0000".toCharArray();
52
53     private final List<InternalScene> namedScenes = new LinkedList<>();
54     private boolean genList = false;
55     ScheduledFuture<?> generateReachableScenesScheduledFuture;
56
57     private SceneManager sceneManager;
58     private SceneStatusListener discovery;
59
60     public static final String NAMEND_SCENE_QUERY = "/apartment/zones/*(ZoneID)/groups/*(group)/scenes/*(scene,name)";
61     public static final String REACHABLE_SCENE_QUERY = "/json/zone/getReachableScenes?id=";
62     public static final String REACHABLE_GROUPS_QUERY = "/json/apartment/getReachableGroups?token=";
63
64     /**
65      * Creates a new {@link SceneDiscovery} with managed scene by the {@link SceneManager}
66      *
67      * @param sceneManager must not be null
68      */
69     public SceneDiscovery(SceneManager sceneManager) {
70         this.sceneManager = sceneManager;
71     }
72
73     /**
74      * Creates a new {@link SceneDiscovery} and generates only a list of all scenes, if genList is true.
75      *
76      * @param genList yes/no (true/false)
77      */
78     public SceneDiscovery(boolean genList) {
79         this.genList = genList;
80     }
81
82     /**
83      * Generates all named, reachable group, apartment and zone scenes.
84      *
85      * @param connectionManager must not be null
86      * @param structureManager must not be null
87      */
88     public void generateAllScenes(ConnectionManager connectionManager, StructureManager structureManager) {
89         generateNamedScenes(connectionManager);
90         generateApartmentScence();
91         generateZoneScenes(connectionManager, structureManager);
92         generateReachableScenes(connectionManager, structureManager);
93     }
94
95     /**
96      * Generates all named scenes.
97      *
98      * @param connectionManager must not be null
99      * @return true, if successful otherwise false
100      */
101     public boolean generateNamedScenes(ConnectionManager connectionManager) {
102         JsonObject responsJsonObj = connectionManager.getDigitalSTROMAPI().query(connectionManager.getSessionToken(),
103                 NAMEND_SCENE_QUERY);
104         if (responsJsonObj == null) {
105             scenesGenerated[0] = '2';
106             sceneManager.scenesGenerated(scenesGenerated);
107             return false;
108         } else {
109             addScenesToList(responsJsonObj);
110             scenesGenerated[0] = '1';
111             sceneManager.scenesGenerated(scenesGenerated);
112             return true;
113         }
114     }
115
116     /**
117      * Generates all apartment scenes.
118      */
119     public void generateApartmentScence() {
120         for (ApartmentSceneEnum apartmentScene : ApartmentSceneEnum.values()) {
121
122             InternalScene scene = new InternalScene(null, null, apartmentScene.getSceneNumber(),
123                     "Apartment-Scene: " + apartmentScene.toString().toLowerCase().replace("_", " "));
124             if (genList) {
125                 this.namedScenes.add(scene);
126             } else {
127                 sceneDiscoverd(scene);
128             }
129         }
130         scenesGenerated[1] = '1';
131         sceneManager.scenesGenerated(scenesGenerated);
132     }
133
134     private void addScenesToList(JsonObject resultJsonObj) {
135         if (resultJsonObj.get(JSONApiResponseKeysEnum.ZONES.getKey()) != null
136                 && resultJsonObj.get(JSONApiResponseKeysEnum.ZONES.getKey()).isJsonArray()) {
137             JsonArray zones = resultJsonObj.get(JSONApiResponseKeysEnum.ZONES.getKey()).getAsJsonArray();
138             for (int i = 0; i < zones.size(); i++) {
139
140                 if (((JsonObject) zones.get(i)).get(JSONApiResponseKeysEnum.GROUPS.getKey()).isJsonArray()) {
141                     JsonArray groups = ((JsonObject) zones.get(i)).get(JSONApiResponseKeysEnum.GROUPS.getKey())
142                             .getAsJsonArray();
143
144                     for (int j = 0; j < groups.size(); j++) {
145
146                         if (((JsonObject) groups.get(j)).get("scenes") != null
147                                 && ((JsonObject) groups.get(j)).get("scenes").isJsonArray()) {
148                             JsonArray scenes = ((JsonObject) groups.get(j)).get("scenes").getAsJsonArray();
149                             for (int k = 0; k < scenes.size(); k++) {
150                                 if (scenes.get(k).isJsonObject()) {
151                                     JsonObject sceneJsonObject = ((JsonObject) scenes.get(k));
152                                     int zoneID = ((JsonObject) zones.get(i)).get("ZoneID").getAsInt();
153                                     short groupID = ((JsonObject) groups.get(j)).get("group").getAsShort();
154                                     InternalScene scene = new InternalScene(zoneID, groupID,
155                                             sceneJsonObject.get("scene").getAsShort(),
156                                             sceneJsonObject.get("name").getAsString());
157                                     if (genList) {
158                                         this.namedScenes.add(scene);
159                                     } else {
160                                         sceneDiscoverd(scene);
161                                     }
162                                 }
163                             }
164                         }
165                     }
166                 }
167             }
168         }
169     }
170
171     /**
172      * Generates all zone scenes.
173      *
174      * @param connectionManager must not be null
175      * @param structureManager must not be null
176      * @return success true otherwise false
177      */
178     public boolean generateZoneScenes(ConnectionManager connectionManager, StructureManager structureManager) {
179         HashMap<Integer, List<Short>> reachableGroups = getReachableGroups(connectionManager);
180
181         if (reachableGroups != null) {
182             for (Integer zoneID : reachableGroups.keySet()) {
183                 if (!reachableGroups.get(zoneID).isEmpty()) {
184                     for (ZoneSceneEnum zoneScene : ZoneSceneEnum.values()) {
185                         String sceneName = "Zone-Scene: Zone: ";
186                         if (structureManager.getZoneName(zoneID) != null
187                                 && !structureManager.getZoneName(zoneID).isEmpty()) {
188                             sceneName = sceneName + structureManager.getZoneName(zoneID);
189
190                         } else {
191                             sceneName = sceneName + zoneID;
192                         }
193                         sceneName = sceneName + " Scene: " + zoneScene.toString().toLowerCase().replace("_", " ");
194                         InternalScene scene = new InternalScene(zoneID, null, zoneScene.getSceneNumber(), sceneName);
195                         if (genList) {
196                             this.namedScenes.add(scene);
197                         } else {
198                             sceneDiscoverd(scene);
199                         }
200                     }
201                 }
202             }
203
204             scenesGenerated[2] = '1';
205             sceneManager.scenesGenerated(scenesGenerated);
206             return true;
207         }
208         scenesGenerated[2] = '2';
209         sceneManager.scenesGenerated(scenesGenerated);
210         return false;
211     }
212
213     /**
214      *
215      */
216     public void stop() {
217         if (generateReachableScenesScheduledFuture != null) {
218             generateReachableScenesScheduledFuture.cancel(true);
219             generateReachableScenesScheduledFuture = null;
220         }
221     }
222
223     /**
224      * Generates all reachable scenes.
225      *
226      * @param connectionManager must not be null
227      * @param structureManager must not be null
228      */
229     public void generateReachableScenes(final ConnectionManager connectionManager,
230             final StructureManager structureManager) {
231         if (generateReachableScenesScheduledFuture == null || generateReachableScenesScheduledFuture.isCancelled()) {
232             generateReachableScenesScheduledFuture = ThreadPoolManager.getScheduledPool(Config.THREADPOOL_NAME)
233                     .scheduleWithFixedDelay(new Runnable() {
234
235                         HashMap<Integer, List<Short>> reachableGroups = getReachableGroups(connectionManager);
236                         Iterator<Integer> zoneIdInter = null;
237                         Iterator<Short> groupIdInter = null;
238                         Integer zoneID = null;
239
240                         @Override
241                         public void run() {
242                             if (reachableGroups != null) {
243                                 if (zoneIdInter == null) {
244                                     zoneIdInter = reachableGroups.keySet().iterator();
245                                 }
246                                 if (groupIdInter == null) {
247                                     if (zoneIdInter.hasNext()) {
248                                         zoneID = zoneIdInter.next();
249                                         groupIdInter = reachableGroups.get(zoneID).iterator();
250                                     } else {
251                                         zoneID = null;
252                                         scenesGenerated[3] = '1';
253                                         sceneManager.scenesGenerated(scenesGenerated);
254                                         generateReachableScenesScheduledFuture.cancel(true);
255                                     }
256                                 }
257                                 if (zoneID != null) {
258                                     if (groupIdInter != null) {
259                                         Short groupID = null;
260                                         if (groupIdInter.hasNext()) {
261                                             groupID = groupIdInter.next();
262                                         } else {
263                                             groupIdInter = null;
264                                         }
265                                         if (groupID != null) {
266                                             if (ApplicationGroup.Color.YELLOW
267                                                     .equals(ApplicationGroup.getGroup(groupID).getColor())) {
268                                                 discoverScene(SceneEnum.AUTO_OFF.getSceneNumber(), groupID);
269                                             }
270                                             String response = connectionManager.getHttpTransport()
271                                                     .execute(REACHABLE_SCENE_QUERY + zoneID + "&groupID=" + groupID
272                                                             + "&token=" + connectionManager.getSessionToken());
273                                             if (response == null) {
274                                                 scenesGenerated[3] = '2';
275                                                 sceneManager.scenesGenerated(scenesGenerated);
276                                                 logger.debug(
277                                                         "Reachable scenes for zone {} and group {} cant be generated, because the dSS does not answer",
278                                                         zoneID, groupID);
279                                                 generateReachableScenesScheduledFuture.cancel(true);
280                                                 return;
281                                             } else {
282                                                 JsonObject responsJsonObj = JSONResponseHandler.toJsonObject(response);
283                                                 if (JSONResponseHandler.checkResponse(responsJsonObj)) {
284                                                     JsonObject resultJsonObj = JSONResponseHandler
285                                                             .getResultJsonObject(responsJsonObj);
286                                                     if (resultJsonObj
287                                                             .get(JSONApiResponseKeysEnum.REACHABLE_SCENES.getKey())
288                                                             .isJsonArray()) {
289                                                         JsonArray scenes = resultJsonObj
290                                                                 .get(JSONApiResponseKeysEnum.REACHABLE_SCENES.getKey())
291                                                                 .getAsJsonArray();
292                                                         if (scenes != null) {
293                                                             for (int i = 0; i < scenes.size(); i++) {
294                                                                 discoverScene(scenes.get(i).getAsShort(), groupID);
295                                                             }
296                                                         }
297                                                     }
298                                                 }
299                                             }
300                                         }
301                                     }
302                                 }
303                             }
304                         }
305
306                         private void discoverScene(short sceneNumber, short groupID) {
307                             String sceneName = null;
308                             if (SceneEnum.getScene(sceneNumber) != null) {
309                                 if (structureManager.getZoneName(zoneID) != null
310                                         && !structureManager.getZoneName(zoneID).isEmpty()) {
311                                     sceneName = "Zone: " + structureManager.getZoneName(zoneID);
312
313                                 } else {
314                                     sceneName = "Zone: " + zoneID;
315                                 }
316                                 if (structureManager.getZoneGroupName(zoneID, groupID) != null
317                                         && !structureManager.getZoneGroupName(zoneID, groupID).isEmpty()) {
318                                     sceneName = sceneName + " Group: "
319                                             + structureManager.getZoneGroupName(zoneID, groupID);
320                                 } else {
321                                     sceneName = sceneName + " Group: " + groupID;
322                                 }
323                                 sceneName = sceneName + " Scene: "
324                                         + SceneEnum.getScene(sceneNumber).toString().toLowerCase().replace("_", " ");
325                             }
326                             InternalScene scene = new InternalScene(zoneID, groupID, sceneNumber, sceneName);
327
328                             if (genList) {
329                                 namedScenes.add(scene);
330                             } else {
331                                 sceneDiscoverd(scene);
332                             }
333                         }
334                     }, 0, 500, TimeUnit.MILLISECONDS);
335         }
336     }
337
338     private HashMap<Integer, List<Short>> getReachableGroups(ConnectionManager connectionManager) {
339         HashMap<Integer, List<Short>> reachableGroupsMap = null;
340         String response = connectionManager.getHttpTransport()
341                 .execute(SceneDiscovery.REACHABLE_GROUPS_QUERY + connectionManager.getSessionToken());
342         if (response == null) {
343             return null;
344         } else {
345             JsonObject responsJsonObj = JSONResponseHandler.toJsonObject(response);
346             if (JSONResponseHandler.checkResponse(responsJsonObj)) {
347                 JsonObject resultJsonObj = JSONResponseHandler.getResultJsonObject(responsJsonObj);
348                 if (resultJsonObj.get(JSONApiResponseKeysEnum.ZONES.getKey()) instanceof JsonArray) {
349                     JsonArray zones = (JsonArray) resultJsonObj.get(JSONApiResponseKeysEnum.ZONES.getKey());
350                     reachableGroupsMap = new HashMap<>(zones.size());
351                     List<Short> groupList;
352                     for (int i = 0; i < zones.size(); i++) {
353                         if (((JsonObject) zones.get(i))
354                                 .get(JSONApiResponseKeysEnum.GROUPS.getKey()) instanceof JsonArray) {
355                             JsonArray groups = (JsonArray) ((JsonObject) zones.get(i))
356                                     .get(JSONApiResponseKeysEnum.GROUPS.getKey());
357                             groupList = new LinkedList<>();
358                             for (int k = 0; k < groups.size(); k++) {
359                                 groupList.add(groups.get(k).getAsShort());
360                             }
361                             reachableGroupsMap.put(((JsonObject) zones.get(i)).get("zoneID").getAsInt(), groupList);
362                         }
363                     }
364                 }
365             }
366         }
367         return reachableGroupsMap;
368     }
369
370     /**
371      * Informs the registered {@link SceneStatusListener} as scene discovery about a new scene.
372      *
373      * @param scene that was discoverd
374      */
375     public void sceneDiscoverd(InternalScene scene) {
376         if (scene != null) {
377             if (SceneEnum.containsScene(scene.getSceneID())) {
378                 if (!isStandardScene(scene.getSceneID())) {
379                     if (this.discovery != null) {
380                         this.discovery.onSceneAdded(scene);
381                         logger.debug("Inform scene discovery about added scene with id: {}", scene.getID());
382                     } else {
383                         logger.debug(
384                                 "Can't inform scene discovery about added scene with id: {} because scene discovery is disabled",
385                                 scene.getID());
386                     }
387                 }
388                 this.sceneManager.addInternalScene(scene);
389             } else {
390                 logger.error("Added scene with id: {} is a not usage scene!", scene.getID());
391             }
392         }
393     }
394
395     private boolean isStandardScene(short sceneID) {
396         switch (SceneEnum.getScene(sceneID)) {
397             case INCREMENT:
398             case DECREMENT:
399             case STOP:
400             case MINIMUM:
401             case MAXIMUM:
402             case AUTO_OFF:
403             case DEVICE_ON:
404             case DEVICE_OFF:
405             case DEVICE_STOP:
406                 return true;
407             default:
408                 return false;
409         }
410     }
411
412     /**
413      * Registers the given {@link SceneStatusListener} as scene discovery.
414      *
415      * @param listener to register
416      */
417     public void registerSceneDiscovery(SceneStatusListener listener) {
418         this.discovery = listener;
419     }
420
421     /**
422      * Unregisters the {@link SceneStatusListener} as scene discovery from this {@link InternalScene}.
423      */
424     public void unRegisterDiscovery() {
425         this.discovery = null;
426     }
427
428     /**
429      * Returns the list of all generated {@link InternalScene}'s, if the list shall generated.
430      *
431      * @return List of all {@link InternalScene} or null
432      */
433     public List<InternalScene> getNamedSceneList() {
434         if (genList) {
435             return this.namedScenes;
436         } else {
437             if (sceneManager != null) {
438                 sceneManager.getScenes();
439             }
440         }
441         return null;
442     }
443
444     @Override
445     public String toString() {
446         return this.namedScenes.toString();
447     }
448 }