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.dmx.internal;
15 import static org.openhab.binding.dmx.internal.DmxBindingConstants.CHANNEL_MUTE;
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;
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;
46 * The {@link DmxBridgeHandler} is an abstract class with base functions
49 * @author Jan N. Klug - Initial contribution
52 public abstract class DmxBridgeHandler extends BaseBridgeHandler {
53 public static final int DEFAULT_REFRESH_RATE = 20;
55 private final Logger logger = LoggerFactory.getLogger(DmxBridgeHandler.class);
57 protected Universe universe = new Universe(0); // default universe
59 private @Nullable ScheduledFuture<?> senderJob;
60 private boolean isMuted = false;
61 private int refreshTime = 1000 / DEFAULT_REFRESH_RATE;
63 public DmxBridgeHandler(Bridge dmxBridge) {
68 public void handleCommand(ChannelUID channelUID, Command command) {
69 switch (channelUID.getId()) {
71 if (command instanceof OnOffType) {
72 isMuted = command.equals(OnOffType.ON);
74 logger.debug("command {} not supported in channel {}:mute", command.getClass(),
79 logger.warn("Channel {} not supported in bridge {}", channelUID.getId(), this.thing.getUID());
84 * get a DMX channel from the bridge
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
90 public DmxChannel getDmxChannel(BaseDmxChannel channel, Thing thing) {
91 return universe.registerChannel(channel, thing);
95 * remove a thing from all channels in the universe
97 * @param thing the thing that shall be removed
99 public void unregisterDmxChannels(Thing thing) {
100 universe.unregisterChannels(thing);
104 * get the universe associated with this bridge
106 * @return the DMX universe id
108 public int getUniverseId() {
109 return universe.getUniverseId();
113 public void thingUpdated(Thing thing) {
114 updateConfiguration();
118 * open the connection to send DMX data to
120 protected abstract void openConnection();
123 * close the connection to send DMX data to
125 protected abstract void closeConnection();
128 * close the connection to send DMX data and update thing Status
130 * @param statusDetail ThingStatusDetail for thingStatus OFFLINE
131 * @param description string giving the reason for closing the connection
133 protected void closeConnection(ThingStatusDetail statusDetail, String description) {
134 updateStatus(ThingStatus.OFFLINE, statusDetail, description);
139 * send the buffer of the current universe
141 protected abstract void sendDmxData();
144 * install the sending and updating scheduler
146 protected void installScheduler() {
147 if (senderJob != null) {
148 uninstallScheduler();
150 if (refreshTime > 0) {
151 senderJob = scheduler.scheduleAtFixedRate(() -> {
152 logger.trace("runnable packet sender for universe {} called, state {}/{}", universe.getUniverseId(),
153 getThing().getStatus(), isMuted);
157 logger.trace("bridge {} is muted", getThing().getUID());
159 }, 1, refreshTime, TimeUnit.MILLISECONDS);
160 logger.trace("started scheduler for thing {}", this.thing.getUID());
162 logger.info("refresh disabled for thing {}", this.thing.getUID());
167 * uninstall the sending and updating scheduler
169 protected void uninstallScheduler() {
170 if (senderJob != null) {
171 if (!senderJob.isCancelled()) {
172 senderJob.cancel(true);
176 logger.trace("stopping scheduler for thing {}", this.thing.getUID());
181 public void childHandlerDisposed(ThingHandler thingHandler, Thing thing) {
182 universe.unregisterChannels(thing);
186 * get the configuration and update the bridge
188 protected void updateConfiguration() {
189 DmxBridgeHandlerConfiguration configuration = getConfig().as(DmxBridgeHandlerConfiguration.class);
191 if (!configuration.applycurve.isEmpty()) {
192 universe.setDimCurveChannels(configuration.applycurve);
195 int refreshRate = configuration.refreshrate;
196 if (refreshRate > 0) {
197 refreshTime = (int) (1000.0 / refreshRate);
202 logger.debug("set refreshTime to {} ms in thing {}", refreshTime, this.thing.getUID());
208 public void dispose() {
209 uninstallScheduler();
213 * set the universe id and make sure it observes the limits
215 * @param universeConfig ConfigurationObject
216 * @param minUniverseId the minimum id allowed by the bridge
217 * @param maxUniverseId the maximum id allowed by the bridge
219 protected void setUniverse(int universeConfig, int minUniverseId, int maxUniverseId) {
220 int universeId = minUniverseId;
221 universeId = Util.coerceToRange(universeConfig, minUniverseId, maxUniverseId, logger, "universeId");
223 if (universe.getUniverseId() != universeId) {
224 universe.rename(universeId);
229 * sends an immediate fade to the DMX output (for rule actions)
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
235 public void immediateFade(String channelString, String fadeString, Boolean resumeAfter) {
236 // parse channel config
237 List<DmxChannel> channels = new ArrayList<>();
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));
244 } catch (IllegalArgumentException e) {
245 logger.warn("invalid channel configuration: {}", channelString);
250 List<ValueSet> value = ValueSet.parseChaseConfig(fadeString);
251 if (value.isEmpty()) {
252 logger.warn("invalid fade configuration: {}", fadeString);
257 int channelCounter = 0;
258 for (DmxChannel channel : channels) {
260 channel.suspendAction();
262 channel.clearAction();
264 for (ValueSet step : value) {
265 channel.addChannelAction(
266 new FadeAction(step.getFadeTime(), step.getValue(channelCounter), step.getHoldTime()));
269 channel.addChannelAction(new ResumeAction());
276 public Collection<Class<? extends ThingHandlerService>> getServices() {
277 return List.of(DmxActions.class);