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.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;
47 * The {@link DmxBridgeHandler} is an abstract class with base functions
50 * @author Jan N. Klug - Initial contribution
53 public abstract class DmxBridgeHandler extends BaseBridgeHandler {
54 public static final int DEFAULT_REFRESH_RATE = 20;
56 private final Logger logger = LoggerFactory.getLogger(DmxBridgeHandler.class);
58 protected Universe universe = new Universe(0); // default universe
60 private @Nullable ScheduledFuture<?> senderJob;
61 private boolean isMuted = false;
62 private int refreshTime = 1000 / DEFAULT_REFRESH_RATE;
64 public DmxBridgeHandler(Bridge dmxBridge) {
69 public void handleCommand(ChannelUID channelUID, Command command) {
70 switch (channelUID.getId()) {
72 if (command instanceof OnOffType) {
73 isMuted = command.equals(OnOffType.ON);
75 logger.debug("command {} not supported in channel {}:mute", command.getClass(),
80 logger.warn("Channel {} not supported in bridge {}", channelUID.getId(), this.thing.getUID());
85 * get a DMX channel from the bridge
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
91 public DmxChannel getDmxChannel(BaseDmxChannel channel, Thing thing) {
92 return universe.registerChannel(channel, thing);
96 * remove a thing from all channels in the universe
98 * @param thing the thing that shall be removed
100 public void unregisterDmxChannels(Thing thing) {
101 universe.unregisterChannels(thing);
105 * get the universe associated with this bridge
107 * @return the DMX universe id
109 public int getUniverseId() {
110 return universe.getUniverseId();
114 public void thingUpdated(Thing thing) {
115 updateConfiguration();
119 * open the connection to send DMX data to
121 protected abstract void openConnection();
124 * close the connection to send DMX data to
126 protected abstract void closeConnection();
129 * close the connection to send DMX data and update thing Status
131 * @param statusDetail ThingStatusDetail for thingStatus OFFLINE
132 * @param description string giving the reason for closing the connection
134 protected void closeConnection(ThingStatusDetail statusDetail, String description) {
135 updateStatus(ThingStatus.OFFLINE, statusDetail, description);
140 * send the buffer of the current universe
142 protected abstract void sendDmxData();
145 * install the sending and updating scheduler
147 protected void installScheduler() {
148 if (senderJob != null) {
149 uninstallScheduler();
151 if (refreshTime > 0) {
152 senderJob = scheduler.scheduleAtFixedRate(() -> {
153 logger.trace("runnable packet sender for universe {} called, state {}/{}", universe.getUniverseId(),
154 getThing().getStatus(), isMuted);
158 logger.trace("bridge {} is muted", getThing().getUID());
160 }, 1, refreshTime, TimeUnit.MILLISECONDS);
161 logger.trace("started scheduler for thing {}", this.thing.getUID());
163 logger.info("refresh disabled for thing {}", this.thing.getUID());
168 * uninstall the sending and updating scheduler
170 protected void uninstallScheduler() {
171 if (senderJob != null) {
172 if (!senderJob.isCancelled()) {
173 senderJob.cancel(true);
177 logger.trace("stopping scheduler for thing {}", this.thing.getUID());
182 public void childHandlerDisposed(ThingHandler thingHandler, Thing thing) {
183 universe.unregisterChannels(thing);
187 * get the configuration and update the bridge
189 protected void updateConfiguration() {
190 DmxBridgeHandlerConfiguration configuration = getConfig().as(DmxBridgeHandlerConfiguration.class);
192 if (!configuration.applycurve.isEmpty()) {
193 universe.setDimCurveChannels(configuration.applycurve);
196 int refreshRate = configuration.refreshrate;
197 if (refreshRate > 0) {
198 refreshTime = (int) (1000.0 / refreshRate);
203 logger.debug("set refreshTime to {} ms in thing {}", refreshTime, this.thing.getUID());
209 public void dispose() {
210 uninstallScheduler();
214 * set the universe id and make sure it observes the limits
216 * @param universeConfig ConfigurationObject
217 * @param minUniverseId the minimum id allowed by the bridge
218 * @param maxUniverseId the maximum id allowed by the bridge
220 protected void setUniverse(int universeConfig, int minUniverseId, int maxUniverseId) {
221 int universeId = minUniverseId;
222 universeId = Util.coerceToRange(universeConfig, minUniverseId, maxUniverseId, logger, "universeId");
224 if (universe.getUniverseId() != universeId) {
225 universe.rename(universeId);
230 * sends an immediate fade to the DMX output (for rule actions)
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
236 public void immediateFade(String channelString, String fadeString, Boolean resumeAfter) {
237 // parse channel config
238 List<DmxChannel> channels = new ArrayList<>();
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));
245 } catch (IllegalArgumentException e) {
246 logger.warn("invalid channel configuration: {}", channelString);
251 List<ValueSet> value = ValueSet.parseChaseConfig(fadeString);
252 if (value.isEmpty()) {
253 logger.warn("invalid fade configuration: {}", fadeString);
258 int channelCounter = 0;
259 for (DmxChannel channel : channels) {
261 channel.suspendAction();
263 channel.clearAction();
265 for (ValueSet step : value) {
266 channel.addChannelAction(
267 new FadeAction(step.getFadeTime(), step.getValue(channelCounter), step.getHoldTime()));
270 channel.addChannelAction(new ResumeAction());
277 public Collection<Class<? extends ThingHandlerService>> getServices() {
278 return Collections.singletonList(DmxActions.class);