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) {
267 if (ApplicationGroup.Color.YELLOW
268 .equals(ApplicationGroup.getGroup(groupID).getColor())) {
269 discoverScene(SceneEnum.AUTO_OFF.getSceneNumber(), groupID);
271 String response = connectionManager.getHttpTransport()
272 .execute(REACHABLE_SCENE_QUERY + zoneID + "&groupID=" + groupID
273 + "&token=" + connectionManager.getSessionToken());
274 if (response == null) {
275 scenesGenerated[3] = '2';
276 sceneManager.scenesGenerated(scenesGenerated);
278 "Reachable scenes for zone {} and group {} cant be generated, because the dSS does not answer",
280 generateReachableScenesScheduledFuture.cancel(true);
283 JsonObject responsJsonObj = JSONResponseHandler.toJsonObject(response);
284 if (JSONResponseHandler.checkResponse(responsJsonObj)) {
285 JsonObject resultJsonObj = JSONResponseHandler
286 .getResultJsonObject(responsJsonObj);
288 .get(JSONApiResponseKeysEnum.REACHABLE_SCENES.getKey())
290 JsonArray scenes = resultJsonObj
291 .get(JSONApiResponseKeysEnum.REACHABLE_SCENES.getKey())
293 if (scenes != null) {
294 for (int i = 0; i < scenes.size(); i++) {
295 discoverScene(scenes.get(i).getAsShort(), groupID);
307 private void discoverScene(short sceneNumber, short groupID) {
308 String sceneName = null;
309 if (SceneEnum.getScene(sceneNumber) != null) {
310 if (structureManager.getZoneName(zoneID) != null
311 && !structureManager.getZoneName(zoneID).isEmpty()) {
312 sceneName = "Zone: " + structureManager.getZoneName(zoneID);
315 sceneName = "Zone: " + zoneID;
317 if (structureManager.getZoneGroupName(zoneID, groupID) != null
318 && !structureManager.getZoneGroupName(zoneID, groupID).isEmpty()) {
319 sceneName = sceneName + " Group: "
320 + structureManager.getZoneGroupName(zoneID, groupID);
322 sceneName = sceneName + " Group: " + groupID;
324 sceneName = sceneName + " Scene: "
325 + SceneEnum.getScene(sceneNumber).toString().toLowerCase().replace("_", " ");
327 InternalScene scene = new InternalScene(zoneID, groupID, sceneNumber, sceneName);
330 namedScenes.add(scene);
332 sceneDiscoverd(scene);
335 }, 0, 500, TimeUnit.MILLISECONDS);
339 private HashMap<Integer, List<Short>> getReachableGroups(ConnectionManager connectionManager) {
340 HashMap<Integer, List<Short>> reachableGroupsMap = null;
341 String response = connectionManager.getHttpTransport()
342 .execute(SceneDiscovery.REACHABLE_GROUPS_QUERY + connectionManager.getSessionToken());
343 if (response == null) {
346 JsonObject responsJsonObj = JSONResponseHandler.toJsonObject(response);
347 if (JSONResponseHandler.checkResponse(responsJsonObj)) {
348 JsonObject resultJsonObj = JSONResponseHandler.getResultJsonObject(responsJsonObj);
349 if (resultJsonObj.get(JSONApiResponseKeysEnum.ZONES.getKey()) instanceof JsonArray) {
350 JsonArray zones = (JsonArray) resultJsonObj.get(JSONApiResponseKeysEnum.ZONES.getKey());
351 reachableGroupsMap = new HashMap<>(zones.size());
352 List<Short> groupList;
353 for (int i = 0; i < zones.size(); i++) {
354 if (((JsonObject) zones.get(i))
355 .get(JSONApiResponseKeysEnum.GROUPS.getKey()) instanceof JsonArray) {
356 JsonArray groups = (JsonArray) ((JsonObject) zones.get(i))
357 .get(JSONApiResponseKeysEnum.GROUPS.getKey());
358 groupList = new LinkedList<>();
359 for (int k = 0; k < groups.size(); k++) {
360 groupList.add(groups.get(k).getAsShort());
362 reachableGroupsMap.put(((JsonObject) zones.get(i)).get("zoneID").getAsInt(), groupList);
368 return reachableGroupsMap;
372 * Informs the registered {@link SceneStatusListener} as scene discovery about a new scene.
374 * @param scene that was discoverd
376 public void sceneDiscoverd(InternalScene scene) {
378 if (SceneEnum.containsScene(scene.getSceneID())) {
379 if (!isStandardScene(scene.getSceneID())) {
380 if (this.discovery != null) {
381 this.discovery.onSceneAdded(scene);
382 logger.debug("Inform scene discovery about added scene with id: {}", scene.getID());
385 "Can't inform scene discovery about added scene with id: {} because scene discovery is disabled",
389 this.sceneManager.addInternalScene(scene);
391 logger.error("Added scene with id: {} is a not usage scene!", scene.getID());
396 private boolean isStandardScene(short sceneID) {
397 switch (SceneEnum.getScene(sceneID)) {
414 * Registers the given {@link SceneStatusListener} as scene discovery.
416 * @param listener to register
418 public void registerSceneDiscovery(SceneStatusListener listener) {
419 this.discovery = listener;
423 * Unregisters the {@link SceneStatusListener} as scene discovery from this {@link InternalScene}.
425 public void unRegisterDiscovery() {
426 this.discovery = null;
430 * Returns the list of all generated {@link InternalScene}'s, if the list shall generated.
432 * @return List of all {@link InternalScene} or null
434 public List<InternalScene> getNamedSceneList() {
436 return this.namedScenes;
438 if (sceneManager != null) {
439 sceneManager.getScenes();
446 public String toString() {
447 return this.namedScenes.toString();