]> git.basschouten.com Git - openhab-addons.git/blob
8af7306742d48572d9970242efc38c3896927111
[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.dmx.internal;
14
15 import static org.openhab.binding.dmx.internal.DmxBindingConstants.CHANNEL_MUTE;
16
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.List;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
23
24 import org.openhab.binding.dmx.internal.action.DmxActions;
25 import org.openhab.binding.dmx.internal.action.FadeAction;
26 import org.openhab.binding.dmx.internal.action.ResumeAction;
27 import org.openhab.binding.dmx.internal.config.DmxBridgeHandlerConfiguration;
28 import org.openhab.binding.dmx.internal.multiverse.BaseDmxChannel;
29 import org.openhab.binding.dmx.internal.multiverse.DmxChannel;
30 import org.openhab.binding.dmx.internal.multiverse.Universe;
31 import org.openhab.core.library.types.OnOffType;
32 import org.openhab.core.thing.Bridge;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.ThingStatus;
36 import org.openhab.core.thing.ThingStatusDetail;
37 import org.openhab.core.thing.binding.BaseBridgeHandler;
38 import org.openhab.core.thing.binding.ThingHandler;
39 import org.openhab.core.thing.binding.ThingHandlerService;
40 import org.openhab.core.types.Command;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 /**
45  * The {@link DmxBridgeHandler} is an abstract class with base functions
46  * for DMX Bridges
47  *
48  * @author Jan N. Klug - Initial contribution
49  */
50
51 public abstract class DmxBridgeHandler extends BaseBridgeHandler {
52     public static final int DEFAULT_REFRESH_RATE = 20;
53
54     private final Logger logger = LoggerFactory.getLogger(DmxBridgeHandler.class);
55
56     protected Universe universe;
57
58     private ScheduledFuture<?> senderJob;
59     private boolean isMuted = false;
60     private int refreshTime = 1000 / DEFAULT_REFRESH_RATE;
61
62     public DmxBridgeHandler(Bridge dmxBridge) {
63         super(dmxBridge);
64     }
65
66     @Override
67     public void handleCommand(ChannelUID channelUID, Command command) {
68         switch (channelUID.getId()) {
69             case CHANNEL_MUTE:
70                 if (command instanceof OnOffType) {
71                     isMuted = ((OnOffType) command).equals(OnOffType.ON);
72                 } else {
73                     logger.debug("command {} not supported in channel {}:mute", command.getClass(),
74                             this.thing.getUID());
75                 }
76                 break;
77             default:
78                 logger.warn("Channel {} not supported in bridge {}", channelUID.getId(), this.thing.getUID());
79         }
80     }
81
82     /**
83      * get a DMX channel from the bridge
84      *
85      * @param channel a BaseChannel that identifies the requested channel
86      * @param thing the Thing that requests the channel to track channel usage
87      * @return a Channel object
88      */
89     public DmxChannel getDmxChannel(BaseDmxChannel channel, Thing thing) {
90         return universe.registerChannel(channel, thing);
91     }
92
93     /**
94      * remove a thing from all channels in the universe
95      *
96      * @param thing the thing that shall be removed
97      */
98     public void unregisterDmxChannels(Thing thing) {
99         universe.unregisterChannels(thing);
100     }
101
102     /**
103      * get the universe associated with this bridge
104      *
105      * @return the DMX universe id
106      */
107     public int getUniverseId() {
108         return universe.getUniverseId();
109     }
110
111     /**
112      * rename the universe associated with this bridge
113      *
114      * @param universeId the new DMX universe id
115      */
116     protected void renameUniverse(int universeId) {
117         universe.rename(universeId);
118     }
119
120     @Override
121     public void thingUpdated(Thing thing) {
122         updateConfiguration();
123     }
124
125     /**
126      * open the connection to send DMX data to
127      */
128     protected abstract void openConnection();
129
130     /**
131      * close the connection to send DMX data to
132      */
133     protected abstract void closeConnection();
134
135     /**
136      * close the connection to send DMX data and update thing Status
137      *
138      * @param statusDetail ThingStatusDetail for thingStatus OFFLINE
139      * @param description string giving the reason for closing the connection
140      */
141     protected void closeConnection(ThingStatusDetail statusDetail, String description) {
142         updateStatus(ThingStatus.OFFLINE, statusDetail, description);
143         closeConnection();
144     }
145
146     /**
147      * send the buffer of the current universe
148      */
149     protected abstract void sendDmxData();
150
151     /**
152      * install the sending and updating scheduler
153      */
154     protected void installScheduler() {
155         if (senderJob != null) {
156             uninstallScheduler();
157         }
158         if (refreshTime > 0) {
159             senderJob = scheduler.scheduleAtFixedRate(() -> {
160                 logger.trace("runnable packet sender for universe {} called, state {}/{}", universe.getUniverseId(),
161                         getThing().getStatus(), isMuted);
162                 if (!isMuted) {
163                     sendDmxData();
164                 } else {
165                     logger.trace("bridge {} is muted", getThing().getUID());
166                 }
167             }, 1, refreshTime, TimeUnit.MILLISECONDS);
168             logger.trace("started scheduler for thing {}", this.thing.getUID());
169         } else {
170             logger.info("refresh disabled for thing {}", this.thing.getUID());
171         }
172     }
173
174     /**
175      * uninstall the sending and updating scheduler
176      */
177     protected void uninstallScheduler() {
178         if (senderJob != null) {
179             if (!senderJob.isCancelled()) {
180                 senderJob.cancel(true);
181             }
182             senderJob = null;
183             closeConnection();
184             logger.trace("stopping scheduler for thing {}", this.thing.getUID());
185         }
186     }
187
188     @Override
189     public void childHandlerDisposed(ThingHandler thingHandler, Thing thing) {
190         universe.unregisterChannels(thing);
191     }
192
193     /**
194      * get the configuration and update the bridge
195      */
196     protected void updateConfiguration() {
197         DmxBridgeHandlerConfiguration configuration = getConfig().as(DmxBridgeHandlerConfiguration.class);
198
199         if (!configuration.applycurve.isEmpty()) {
200             universe.setDimCurveChannels(configuration.applycurve);
201         }
202
203         int refreshRate = configuration.refreshrate;
204         if (refreshRate > 0) {
205             refreshTime = (int) (1000.0 / refreshRate);
206         } else {
207             refreshTime = 0;
208         }
209
210         logger.debug("set refreshTime to {} ms in thing {}", refreshTime, this.thing.getUID());
211
212         installScheduler();
213     }
214
215     @Override
216     public void dispose() {
217         uninstallScheduler();
218     }
219
220     /**
221      * set the universe id and make sure it observes the limits
222      *
223      * @param universeConfig ConfigurationObject
224      * @param minUniverseId the minimum id allowed by the bridge
225      * @param maxUniverseId the maximum id allowed by the bridge
226      **/
227     protected void setUniverse(int universeConfig, int minUniverseId, int maxUniverseId) {
228         int universeId = minUniverseId;
229         universeId = Util.coerceToRange(universeConfig, minUniverseId, maxUniverseId, logger, "universeId");
230
231         if (universe == null) {
232             universe = new Universe(universeId);
233         } else if (universe.getUniverseId() != universeId) {
234             universe.rename(universeId);
235         }
236     }
237
238     /**
239      * sends an immediate fade to the DMX output (for rule actions)
240      *
241      * @param channelString a String containing the channels
242      * @param fadeString a String containing the fade/chase definition
243      * @param resumeAfter a boolean if the previous state should be restored
244      */
245     public void immediateFade(String channelString, String fadeString, Boolean resumeAfter) {
246         // parse channel config
247         List<DmxChannel> channels = new ArrayList<>();
248         try {
249             List<BaseDmxChannel> configChannels = BaseDmxChannel.fromString(channelString, getUniverseId());
250             logger.trace("found {} channels in {}", configChannels.size(), this.thing.getUID());
251             for (BaseDmxChannel channel : configChannels) {
252                 channels.add(getDmxChannel(channel, this.thing));
253             }
254         } catch (IllegalArgumentException e) {
255             logger.warn("invalid channel configuration: {}", channelString);
256             return;
257         }
258
259         // parse fade config
260         List<ValueSet> value = ValueSet.parseChaseConfig(fadeString);
261         if (value.isEmpty()) {
262             logger.warn("invalid fade configuration: {}", fadeString);
263             return;
264         }
265
266         // do action
267         Integer channelCounter = 0;
268         for (DmxChannel channel : channels) {
269             if (resumeAfter) {
270                 channel.suspendAction();
271             } else {
272                 channel.clearAction();
273             }
274             for (ValueSet step : value) {
275                 channel.addChannelAction(
276                         new FadeAction(step.getFadeTime(), step.getValue(channelCounter), step.getHoldTime()));
277             }
278             if (resumeAfter) {
279                 channel.addChannelAction(new ResumeAction());
280             }
281             channelCounter++;
282         }
283     }
284
285     @Override
286     public Collection<Class<? extends ThingHandlerService>> getServices() {
287         return Collections.singletonList(DmxActions.class);
288     }
289 }