]> git.basschouten.com Git - openhab-addons.git/blob
33c19aab860e8b86652f394c27721ecea23b1194
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.insteon.internal.device;
14
15 import java.util.ArrayList;
16 import java.util.List;
17
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.eclipse.jdt.annotation.Nullable;
20 import org.openhab.binding.insteon.internal.device.database.LinkDBRecord;
21 import org.openhab.binding.insteon.internal.device.feature.FeatureListener;
22 import org.openhab.binding.insteon.internal.handler.InsteonSceneHandler;
23 import org.openhab.core.library.types.OnOffType;
24 import org.openhab.core.types.State;
25 import org.openhab.core.types.UnDefType;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 /**
30  * The {@link InsteonScene} represents an Insteon scene
31  *
32  * @author Jeremy Setton - Initial contribution
33  */
34 @NonNullByDefault
35 public class InsteonScene implements Scene {
36     public static final int GROUP_MIN = 2;
37     public static final int GROUP_MAX = 254;
38     // limit new scene group minimum to 25 matching the current Insteon app behavior
39     public static final int GROUP_NEW_MIN = 25;
40     public static final int GROUP_NEW_MAX = 254;
41
42     private final Logger logger = LoggerFactory.getLogger(InsteonScene.class);
43
44     private int group;
45     private @Nullable InsteonModem modem;
46     private @Nullable InsteonSceneHandler handler;
47     private List<SceneEntry> entries = new ArrayList<>();
48     private boolean modemDBEntry = false;
49
50     public InsteonScene(int group) {
51         this.group = group;
52     }
53
54     @Override
55     public int getGroup() {
56         return group;
57     }
58
59     public @Nullable InsteonModem getModem() {
60         return modem;
61     }
62
63     public @Nullable InsteonSceneHandler getHandler() {
64         return handler;
65     }
66
67     public List<SceneEntry> getEntries() {
68         synchronized (entries) {
69             return entries.stream().toList();
70         }
71     }
72
73     public List<SceneEntry> getEntries(InsteonAddress address) {
74         return getEntries().stream().filter(entry -> entry.getAddress().equals(address)).toList();
75     }
76
77     public List<InsteonAddress> getDevices() {
78         return getEntries().stream().map(SceneEntry::getAddress).distinct().toList();
79     }
80
81     public List<DeviceFeature> getFeatures() {
82         return getEntries().stream().map(SceneEntry::getFeature).toList();
83     }
84
85     public List<DeviceFeature> getFeatures(InsteonAddress address) {
86         return getEntries(address).stream().map(SceneEntry::getFeature).toList();
87     }
88
89     public State getState() {
90         return getEntries().stream().allMatch(entry -> entry.getState() == UnDefType.NULL) ? UnDefType.NULL
91                 : OnOffType.from(getEntries().stream().filter(entry -> entry.getState() != UnDefType.NULL)
92                         .allMatch(entry -> entry.getState().equals(entry.getOnState())));
93     }
94
95     public boolean hasEntry(InsteonAddress address) {
96         return getEntries().stream().anyMatch(entry -> entry.getAddress().equals(address));
97     }
98
99     public boolean hasEntry(InsteonAddress address, String featureName) {
100         return getEntries().stream().anyMatch(
101                 entry -> entry.getAddress().equals(address) && entry.getFeature().getName().equals(featureName));
102     }
103
104     public boolean hasModemDBEntry() {
105         return modemDBEntry;
106     }
107
108     public boolean isComplete() {
109         InsteonModem modem = getModem();
110         return modem != null && modem.getDB().getRelatedDevices(group).stream().allMatch(this::hasEntry);
111     }
112
113     public void setModem(@Nullable InsteonModem modem) {
114         this.modem = modem;
115     }
116
117     public void setHandler(InsteonSceneHandler handler) {
118         this.handler = handler;
119     }
120
121     public void setHasModemDBEntry(boolean modemDBEntry) {
122         this.modemDBEntry = modemDBEntry;
123     }
124
125     @Override
126     public String toString() {
127         return "group:" + group + "|entries:" + entries.size();
128     }
129
130     /**
131      * Adds an entry to this scene
132      *
133      * @param entry the scene entry to add
134      */
135     private void addEntry(SceneEntry entry) {
136         logger.trace("adding entry to scene {}: {}", group, entry);
137
138         synchronized (entries) {
139             if (entries.add(entry)) {
140                 entry.register();
141             }
142         }
143     }
144
145     /**
146      * Deletes an entry from this scene
147      *
148      * @param entry the scene entry to delete
149      */
150     private void deleteEntry(SceneEntry entry) {
151         synchronized (entries) {
152             if (entries.remove(entry)) {
153                 entry.unregister();
154             }
155         }
156     }
157
158     /**
159      * Deletes all entries from this scene
160      */
161     public void deleteEntries() {
162         getEntries().forEach(this::deleteEntry);
163     }
164
165     /**
166      * Deletes entries for a given device from this scene
167      *
168      * @param address the device address
169      */
170     public void deleteEntries(InsteonAddress address) {
171         logger.trace("removing entries from scene {} for device {}", group, address);
172
173         getEntries(address).forEach(this::deleteEntry);
174     }
175
176     /**
177      * Updates all entries for this scene
178      */
179     public void updateEntries() {
180         synchronized (entries) {
181             entries.clear();
182         }
183
184         InsteonModem modem = getModem();
185         if (modem != null) {
186             for (InsteonAddress address : modem.getDB().getRelatedDevices(group)) {
187                 InsteonDevice device = modem.getInsteonDevice(address);
188                 if (device == null) {
189                     logger.debug("device {} part of scene {} not enabled or configured, ignoring.", address, group);
190                 } else {
191                     updateEntries(device);
192                 }
193             }
194         }
195     }
196
197     /**
198      * Updates entries related to a given device for this scene
199      *
200      * @param device the device
201      */
202     public void updateEntries(InsteonDevice device) {
203         InsteonAddress address = device.getAddress();
204
205         logger.trace("updating entries for scene {} device {}", group, address);
206
207         getEntries(address).forEach(this::deleteEntry);
208
209         InsteonModem modem = getModem();
210         if (modem != null) {
211             for (LinkDBRecord record : device.getLinkDB().getResponderRecords(modem.getAddress(), group)) {
212                 device.getResponderFeatures().stream()
213                         .filter(feature -> feature.getComponentId() == record.getComponentId()).findFirst()
214                         .ifPresent(feature -> addEntry(new SceneEntry(address, feature, record.getData())));
215             }
216         }
217     }
218
219     /**
220      * Resets state for this scene
221      */
222     public void resetState() {
223         logger.trace("resetting state for scene {}", group);
224
225         getEntries().forEach(entry -> entry.setState(UnDefType.NULL));
226     }
227
228     /**
229      * Updates state for this scene
230      */
231     private void updateState() {
232         State state = getState();
233         InsteonSceneHandler handler = getHandler();
234         if (handler != null && state instanceof OnOffType) {
235             handler.updateState(state);
236         }
237     }
238
239     /**
240      * Adds a device feature to this scene
241      *
242      * @param device the device
243      * @param onLevel the feature on level
244      * @param rampRate the feature ramp rate
245      * @param componentId the feature component id
246      */
247     public void addDeviceFeature(InsteonDevice device, int onLevel, @Nullable RampRate rampRate, int componentId) {
248         InsteonModem modem = getModem();
249         if (modem == null || !modem.getDB().isComplete() || !device.getLinkDB().isComplete()) {
250             return;
251         }
252
253         modem.getDB().clearChanges();
254         modem.getDB().markRecordForAddOrModify(device.getAddress(), group, true);
255         modem.getDB().update();
256
257         device.getLinkDB().clearChanges();
258         device.getLinkDB().markRecordForAddOrModify(modem.getAddress(), group, false, new byte[] { (byte) onLevel,
259                 (byte) (rampRate != null ? rampRate.getValue() : 0x00), (byte) componentId });
260         device.getLinkDB().update();
261     }
262
263     /**
264      * Removes a device feature from this scene
265      *
266      * @param device the device
267      * @param componentId the feature component id
268      */
269     public void removeDeviceFeature(InsteonDevice device, int componentId) {
270         InsteonModem modem = getModem();
271         if (modem == null || !modem.getDB().isComplete() || !device.getLinkDB().isComplete()) {
272             return;
273         }
274
275         modem.getDB().clearChanges();
276         modem.getDB().markRecordForDelete(device.getAddress(), group);
277         modem.getDB().update();
278
279         device.getLinkDB().clearChanges();
280         device.getLinkDB().markRecordForDelete(modem.getAddress(), group, false, componentId);
281         device.getLinkDB().update();
282     }
283
284     /**
285      * Initializes this scene
286      */
287     public void initialize() {
288         InsteonModem modem = getModem();
289         if (modem == null || !modem.getDB().isComplete()) {
290             return;
291         }
292
293         if (!modem.getDB().hasBroadcastGroup(group)) {
294             logger.warn("scene {} not found in the modem database.", group);
295             setHasModemDBEntry(false);
296             return;
297         }
298
299         if (!hasModemDBEntry()) {
300             logger.debug("scene {} found in the modem database.", group);
301             setHasModemDBEntry(true);
302         }
303
304         updateEntries();
305     }
306
307     /**
308      * Refreshes this scene
309      */
310     @Override
311     public void refresh() {
312         logger.trace("refreshing scene {}", group);
313
314         initialize();
315
316         InsteonSceneHandler handler = getHandler();
317         if (handler != null) {
318             handler.refresh();
319         }
320     }
321
322     /**
323      * Class that represents a scene entry
324      */
325     public class SceneEntry implements FeatureListener {
326         private InsteonAddress address;
327         private DeviceFeature feature;
328         private byte[] data;
329         private State state = UnDefType.NULL;
330
331         public SceneEntry(InsteonAddress address, DeviceFeature feature, byte[] data) {
332             this.address = address;
333             this.feature = feature;
334             this.data = data;
335         }
336
337         public InsteonAddress getAddress() {
338             return address;
339         }
340
341         public DeviceFeature getFeature() {
342             return feature;
343         }
344
345         public State getOnState() {
346             return OnLevel.getState(Byte.toUnsignedInt(data[0]), feature.getType());
347         }
348
349         public RampRate getRampRate() {
350             return RampRate.valueOf(Byte.toUnsignedInt(data[1]));
351         }
352
353         public State getState() {
354             return state;
355         }
356
357         public void setState(State state) {
358             this.state = state;
359         }
360
361         public void register() {
362             feature.registerListener(this);
363
364             stateUpdated(feature.getState());
365         }
366
367         public void unregister() {
368             feature.unregisterListener(this);
369         }
370
371         @Override
372         public String toString() {
373             String s = address + " " + feature.getName() + " currentState: " + state + " onState: " + getOnState();
374             if (RampRate.supportsFeatureType(feature.getType())) {
375                 s += " rampRate: " + getRampRate();
376             }
377             return s;
378         }
379
380         @Override
381         public void stateUpdated(State state) {
382             setState(state);
383             updateState();
384         }
385
386         @Override
387         public void eventTriggered(String event) {
388             // do nothing
389         }
390     }
391
392     /**
393      * Returns if scene group is valid
394      *
395      * @param group the scene group
396      * @return true if group is an integer within supported range
397      */
398     public static boolean isValidGroup(String group) {
399         try {
400             return isValidGroup(Integer.parseInt(group));
401         } catch (NumberFormatException e) {
402             return false;
403         }
404     }
405
406     /**
407      * Returns if scene group is valid
408      *
409      * @param group the scene group
410      * @return true if group within supported range
411      */
412     public static boolean isValidGroup(int group) {
413         return group >= GROUP_MIN && group <= GROUP_MAX;
414     }
415
416     /**
417      * Factory method for creating a InsteonScene from a scene group and modem
418      *
419      * @param group the scene group
420      * @param modem the scene modem
421      * @return the newly created InsteonScene
422      */
423     public static InsteonScene makeScene(int group, @Nullable InsteonModem modem) {
424         InsteonScene scene = new InsteonScene(group);
425         scene.setModem(modem);
426         return scene;
427     }
428 }