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.structure.scene;
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;
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;
37 import com.google.gson.JsonArray;
38 import com.google.gson.JsonObject;
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}.
44 * @author Michael Ochel - Initial contribution
45 * @author Matthias Siegele - Initial contribution
47 public class SceneDiscovery {
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();
53 private final List<InternalScene> namedScenes = new LinkedList<>();
54 private boolean genList = false;
55 ScheduledFuture<?> generateReachableScenesScheduledFuture;
57 private SceneManager sceneManager;
58 private SceneStatusListener discovery;
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=";
65 * Creates a new {@link SceneDiscovery} with managed scene by the {@link SceneManager}
67 * @param sceneManager must not be null
69 public SceneDiscovery(SceneManager sceneManager) {
70 this.sceneManager = sceneManager;
74 * Creates a new {@link SceneDiscovery} and generates only a list of all scenes, if genList is true.
76 * @param genList yes/no (true/false)
78 public SceneDiscovery(boolean genList) {
79 this.genList = genList;
83 * Generates all named, reachable group, apartment and zone scenes.
85 * @param connectionManager must not be null
86 * @param structureManager must not be null
88 public void generateAllScenes(ConnectionManager connectionManager, StructureManager structureManager) {
89 generateNamedScenes(connectionManager);
90 generateApartmentScence();
91 generateZoneScenes(connectionManager, structureManager);
92 generateReachableScenes(connectionManager, structureManager);
96 * Generates all named scenes.
98 * @param connectionManager must not be null
99 * @return true, if successful otherwise false
101 public boolean generateNamedScenes(ConnectionManager connectionManager) {
102 JsonObject responsJsonObj = connectionManager.getDigitalSTROMAPI().query(connectionManager.getSessionToken(),
104 if (responsJsonObj == null) {
105 scenesGenerated[0] = '2';
106 sceneManager.scenesGenerated(scenesGenerated);
109 addScenesToList(responsJsonObj);
110 scenesGenerated[0] = '1';
111 sceneManager.scenesGenerated(scenesGenerated);
117 * Generates all apartment scenes.
119 public void generateApartmentScence() {
120 for (ApartmentSceneEnum apartmentScene : ApartmentSceneEnum.values()) {
122 InternalScene scene = new InternalScene(null, null, apartmentScene.getSceneNumber(),
123 "Apartment-Scene: " + apartmentScene.toString().toLowerCase().replace("_", " "));
125 this.namedScenes.add(scene);
127 sceneDiscoverd(scene);
130 scenesGenerated[1] = '1';
131 sceneManager.scenesGenerated(scenesGenerated);
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++) {
140 if (((JsonObject) zones.get(i)).get(JSONApiResponseKeysEnum.GROUPS.getKey()).isJsonArray()) {
141 JsonArray groups = ((JsonObject) zones.get(i)).get(JSONApiResponseKeysEnum.GROUPS.getKey())
144 for (int j = 0; j < groups.size(); j++) {
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());
158 this.namedScenes.add(scene);
160 sceneDiscoverd(scene);
172 * Generates all zone scenes.
174 * @param connectionManager must not be null
175 * @param structureManager must not be null
176 * @return success true otherwise false
178 public boolean generateZoneScenes(ConnectionManager connectionManager, StructureManager structureManager) {
179 HashMap<Integer, List<Short>> reachableGroups = getReachableGroups(connectionManager);
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);
191 sceneName = sceneName + zoneID;
193 sceneName = sceneName + " Scene: " + zoneScene.toString().toLowerCase().replace("_", " ");
194 InternalScene scene = new InternalScene(zoneID, null, zoneScene.getSceneNumber(), sceneName);
196 this.namedScenes.add(scene);
198 sceneDiscoverd(scene);
204 scenesGenerated[2] = '1';
205 sceneManager.scenesGenerated(scenesGenerated);
208 scenesGenerated[2] = '2';
209 sceneManager.scenesGenerated(scenesGenerated);
217 if (generateReachableScenesScheduledFuture != null) {
218 generateReachableScenesScheduledFuture.cancel(true);
219 generateReachableScenesScheduledFuture = null;
224 * Generates all reachable scenes.
226 * @param connectionManager must not be null
227 * @param structureManager must not be null
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() {
235 HashMap<Integer, List<Short>> reachableGroups = getReachableGroups(connectionManager);
236 Iterator<Integer> zoneIdInter = null;
237 Iterator<Short> groupIdInter = null;
238 Integer zoneID = null;
242 if (reachableGroups != null) {
243 if (zoneIdInter == null) {
244 zoneIdInter = reachableGroups.keySet().iterator();
246 if (groupIdInter == null) {
247 if (zoneIdInter.hasNext()) {
248 zoneID = zoneIdInter.next();
249 groupIdInter = reachableGroups.get(zoneID).iterator();
252 scenesGenerated[3] = '1';
253 sceneManager.scenesGenerated(scenesGenerated);
254 generateReachableScenesScheduledFuture.cancel(true);
257 if (zoneID != null) {
258 if (groupIdInter != null) {
259 Short groupID = null;
260 if (groupIdInter.hasNext()) {
261 groupID = groupIdInter.next();
265 if (groupID != null) {
266 if (ApplicationGroup.Color.YELLOW
267 .equals(ApplicationGroup.getGroup(groupID).getColor())) {
268 discoverScene(SceneEnum.AUTO_OFF.getSceneNumber(), groupID);
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);
277 "Reachable scenes for zone {} and group {} cant be generated, because the dSS does not answer",
279 generateReachableScenesScheduledFuture.cancel(true);
282 JsonObject responsJsonObj = JSONResponseHandler.toJsonObject(response);
283 if (JSONResponseHandler.checkResponse(responsJsonObj)) {
284 JsonObject resultJsonObj = JSONResponseHandler
285 .getResultJsonObject(responsJsonObj);
287 .get(JSONApiResponseKeysEnum.REACHABLE_SCENES.getKey())
289 JsonArray scenes = resultJsonObj
290 .get(JSONApiResponseKeysEnum.REACHABLE_SCENES.getKey())
292 if (scenes != null) {
293 for (int i = 0; i < scenes.size(); i++) {
294 discoverScene(scenes.get(i).getAsShort(), groupID);
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);
314 sceneName = "Zone: " + zoneID;
316 if (structureManager.getZoneGroupName(zoneID, groupID) != null
317 && !structureManager.getZoneGroupName(zoneID, groupID).isEmpty()) {
318 sceneName = sceneName + " Group: "
319 + structureManager.getZoneGroupName(zoneID, groupID);
321 sceneName = sceneName + " Group: " + groupID;
323 sceneName = sceneName + " Scene: "
324 + SceneEnum.getScene(sceneNumber).toString().toLowerCase().replace("_", " ");
326 InternalScene scene = new InternalScene(zoneID, groupID, sceneNumber, sceneName);
329 namedScenes.add(scene);
331 sceneDiscoverd(scene);
334 }, 0, 500, TimeUnit.MILLISECONDS);
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) {
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());
361 reachableGroupsMap.put(((JsonObject) zones.get(i)).get("zoneID").getAsInt(), groupList);
367 return reachableGroupsMap;
371 * Informs the registered {@link SceneStatusListener} as scene discovery about a new scene.
373 * @param scene that was discoverd
375 public void sceneDiscoverd(InternalScene scene) {
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());
384 "Can't inform scene discovery about added scene with id: {} because scene discovery is disabled",
388 this.sceneManager.addInternalScene(scene);
390 logger.error("Added scene with id: {} is a not usage scene!", scene.getID());
395 private boolean isStandardScene(short sceneID) {
396 switch (SceneEnum.getScene(sceneID)) {
413 * Registers the given {@link SceneStatusListener} as scene discovery.
415 * @param listener to register
417 public void registerSceneDiscovery(SceneStatusListener listener) {
418 this.discovery = listener;
422 * Unregisters the {@link SceneStatusListener} as scene discovery from this {@link InternalScene}.
424 public void unRegisterDiscovery() {
425 this.discovery = null;
429 * Returns the list of all generated {@link InternalScene}'s, if the list shall generated.
431 * @return List of all {@link InternalScene} or null
433 public List<InternalScene> getNamedSceneList() {
435 return this.namedScenes;
437 if (sceneManager != null) {
438 sceneManager.getScenes();
445 public String toString() {
446 return this.namedScenes.toString();