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