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.Collections;
20 import java.util.List;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
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;
45 * The {@link DmxBridgeHandler} is an abstract class with base functions
48 * @author Jan N. Klug - Initial contribution
51 public abstract class DmxBridgeHandler extends BaseBridgeHandler {
52 public static final int DEFAULT_REFRESH_RATE = 20;
54 private final Logger logger = LoggerFactory.getLogger(DmxBridgeHandler.class);
56 protected Universe universe;
58 private ScheduledFuture<?> senderJob;
59 private boolean isMuted = false;
60 private int refreshTime = 1000 / DEFAULT_REFRESH_RATE;
62 public DmxBridgeHandler(Bridge dmxBridge) {
67 public void handleCommand(ChannelUID channelUID, Command command) {
68 switch (channelUID.getId()) {
70 if (command instanceof OnOffType) {
71 isMuted = ((OnOffType) command).equals(OnOffType.ON);
73 logger.debug("command {} not supported in channel {}:mute", command.getClass(),
78 logger.warn("Channel {} not supported in bridge {}", channelUID.getId(), this.thing.getUID());
83 * get a DMX channel from the bridge
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
89 public DmxChannel getDmxChannel(BaseDmxChannel channel, Thing thing) {
90 return universe.registerChannel(channel, thing);
94 * remove a thing from all channels in the universe
96 * @param thing the thing that shall be removed
98 public void unregisterDmxChannels(Thing thing) {
99 universe.unregisterChannels(thing);
103 * get the universe associated with this bridge
105 * @return the DMX universe id
107 public int getUniverseId() {
108 return universe.getUniverseId();
112 * rename the universe associated with this bridge
114 * @param universeId the new DMX universe id
116 protected void renameUniverse(int universeId) {
117 universe.rename(universeId);
121 public void thingUpdated(Thing thing) {
122 updateConfiguration();
126 * open the connection to send DMX data to
128 protected abstract void openConnection();
131 * close the connection to send DMX data to
133 protected abstract void closeConnection();
136 * close the connection to send DMX data and update thing Status
138 * @param statusDetail ThingStatusDetail for thingStatus OFFLINE
139 * @param description string giving the reason for closing the connection
141 protected void closeConnection(ThingStatusDetail statusDetail, String description) {
142 updateStatus(ThingStatus.OFFLINE, statusDetail, description);
147 * send the buffer of the current universe
149 protected abstract void sendDmxData();
152 * install the sending and updating scheduler
154 protected void installScheduler() {
155 if (senderJob != null) {
156 uninstallScheduler();
158 if (refreshTime > 0) {
159 senderJob = scheduler.scheduleAtFixedRate(() -> {
160 logger.trace("runnable packet sender for universe {} called, state {}/{}", universe.getUniverseId(),
161 getThing().getStatus(), isMuted);
165 logger.trace("bridge {} is muted", getThing().getUID());
167 }, 1, refreshTime, TimeUnit.MILLISECONDS);
168 logger.trace("started scheduler for thing {}", this.thing.getUID());
170 logger.info("refresh disabled for thing {}", this.thing.getUID());
175 * uninstall the sending and updating scheduler
177 protected void uninstallScheduler() {
178 if (senderJob != null) {
179 if (!senderJob.isCancelled()) {
180 senderJob.cancel(true);
184 logger.trace("stopping scheduler for thing {}", this.thing.getUID());
189 public void childHandlerDisposed(ThingHandler thingHandler, Thing thing) {
190 universe.unregisterChannels(thing);
194 * get the configuration and update the bridge
196 protected void updateConfiguration() {
197 DmxBridgeHandlerConfiguration configuration = getConfig().as(DmxBridgeHandlerConfiguration.class);
199 if (!configuration.applycurve.isEmpty()) {
200 universe.setDimCurveChannels(configuration.applycurve);
203 int refreshRate = configuration.refreshrate;
204 if (refreshRate > 0) {
205 refreshTime = (int) (1000.0 / refreshRate);
210 logger.debug("set refreshTime to {} ms in thing {}", refreshTime, this.thing.getUID());
216 public void dispose() {
217 uninstallScheduler();
221 * set the universe id and make sure it observes the limits
223 * @param universeConfig ConfigurationObject
224 * @param minUniverseId the minimum id allowed by the bridge
225 * @param maxUniverseId the maximum id allowed by the bridge
227 protected void setUniverse(int universeConfig, int minUniverseId, int maxUniverseId) {
228 int universeId = minUniverseId;
229 universeId = Util.coerceToRange(universeConfig, minUniverseId, maxUniverseId, logger, "universeId");
231 if (universe == null) {
232 universe = new Universe(universeId);
233 } else if (universe.getUniverseId() != universeId) {
234 universe.rename(universeId);
239 * sends an immediate fade to the DMX output (for rule actions)
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
245 public void immediateFade(String channelString, String fadeString, Boolean resumeAfter) {
246 // parse channel config
247 List<DmxChannel> channels = new ArrayList<>();
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));
254 } catch (IllegalArgumentException e) {
255 logger.warn("invalid channel configuration: {}", channelString);
260 List<ValueSet> value = ValueSet.parseChaseConfig(fadeString);
261 if (value.isEmpty()) {
262 logger.warn("invalid fade configuration: {}", fadeString);
267 Integer channelCounter = 0;
268 for (DmxChannel channel : channels) {
270 channel.suspendAction();
272 channel.clearAction();
274 for (ValueSet step : value) {
275 channel.addChannelAction(
276 new FadeAction(step.getFadeTime(), step.getValue(channelCounter), step.getHoldTime()));
279 channel.addChannelAction(new ResumeAction());
286 public Collection<Class<? extends ThingHandlerService>> getServices() {
287 return Collections.singletonList(DmxActions.class);