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