added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.dmx</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@@ -0,0 +1,269 @@
# DMX Binding
The DMX binding integrates DMX devices. There are different output devices supported as well as Dimmers and Chasers.
Each output device (bridges) is representing exactly one universe, each thing is bound to a bridge.
At least one bridge and one thing is needed for the binding to work properly.
## Supported Things
### Bridges
Two DMX over Ethernet devices are supported as DMX output: ArtNet and sACN/E1.31.
The ArtNet bridge can only be operated in unicast mode (broadcast mode is not supported as the specification recommends using it if more than 40 nodes are connected, which is unlikely in the case of a smarthome).
The sACN bridge supports both, unicast and multicast.
Additionally Lib485 devices are supported via the Lib485 bridge.
### Things
The most generic thing is a dimmer.
A dimmer can contain one or more DMX channels.
It can be bound to Switch and Dimmer items.
If more than one DMX channel is defined, the item will be updated according to the state of the first DMX channel.
There are two other things similar to the dimmer thing.
One is the color thing, it can be bound to Switch, Dimmer or Color Items and is best used for RGB lamps.
The second one is the tunable white thing, it allows to control the color temperature of lamps with seperate DMX channels for cool white and warm white.
The last supported thing is a chaser.
It can contain one or more DMX channels and binds to Switch items only.
If the thing receives an ON command all running fades in all channels are either suspended (if resumeAfter is set to true) or cleared and replaced with the fades defined in this thing.
An OFF command stops the fades and either restores the previously suspended fades (if resumeAfter is set to true) or just holds the current values.
If any of the DMX channels in a chaser receives a command from another thing, the status of the chaser is updated to OFF.
Chaser things define a control channel that can be used to dynamically change the chasers fade configuration.
## Discovery
Discovery is not supported at the moment.
You have to add all bridges and things manually.
## Thing Configuration
Since the brightness perception of the human eye is not linear, all bridges support `applycurve`, a list of channels `applycurve` that have a CIE 1931 lightness correction (cf. [Poynton, C.A.: “Gamma” and its Disguises: The Nonlinear Mappings of Intensity in Perception, CRTs, Film and Video, SMPTE Journal Dec. 1993, pp. 1099 - 1108](https://www.poynton.com/PDFs/SMPTE93_Gamma.pdf)) applied.
This list follows the format of the thing channel definition.
This is used regardless of the thing(s) that are associated to the channel.
All bridges can make use of the `refreshrate` option.
It determines at what frequency the DMX output is refreshed.
The achievable refresh rate depends on the number of channels and the output type.
A value of `0` disables the output, the default value is 30 Hz.
### ArtNet Bridge (`artnet-bridge`)
The ArtNet bridge has one mandatory configuration value: network address (`address`).
The network address defines the IP address of the receiving node, it is also allowed to use a FQDN if DNS resolution is available.
If necessary the default port 6454 can be changed by adding `:<port>` to the address.
Multiple receivers can be added, separated by a comma.
The universe (`universe`) can range from 0-32767, this value defaults to 0.
There are two more configuration values that usually don't need to be touched.
The address and port of the sender will be automatically selected by the kernel, if they need to be set to a fixed value, this can be done with `localaddress`.
The format is identical to the receiver address.
Unlike DMX512-A (E1.11), the ArtNet standard allows to suppress repeated transmissions of unchanged universes for a certain time.
This is enabled by default and will re-transmit unchanged data with a fixed refresh rate of 800ms.
If for some reason continuous transmission is needed, the `refreshmode` can be set to `always`, opposed to the default `standard`.
### Lib485 Bridge (`lib485-bridge`)
The Lib485 bridge has one mandatory configuration value: network address (`address`).
This is the host/port where lib485 is running.
This can be an IP address but it is also allowed to use a FQDN if DNS resolution is available.
If necessary the default port 9020 can be changed by adding `:<port>` to the address.
The default address is localhost.
Multiple receivers can be added, separated by a comma.
### sACN/E1.31 Bridge (`sacn-bridge`)
The sACN bridge has one mandatory configuration value: transmission mode (`mode`).
The transmission mode can be set to either `unicast` or `multicast`, where the later one is the default value.
If unicast mode is selected, it is mandatory to define the network address (`address`) of the receiving node.
This can be an IP address but it is also allowed to use a FQDN if DNS resolution is available.
If necessary the default port 5568 can be changed by adding `:<port>` to the address.
Multiple receivers can be added, separated by a comma.
The universe (`universe`) can range from 1-63999, this value defaults to 1.
There are some more configuration values that usually don't need to be touched.
The address and port of the sender will be automatically selected by the kernel, if they need to be set to a fixed value, this can be done with `localaddress`.
The format is identical to the receiver address.
Unlike DMX512-A (E1.11), the E1.31 standard allows to suppress repeated transmissions of unchanged universes for a certain time.
This is enabled by default and will re-transmit unchanged data with a fixed refresh rate of 800ms.
If for some reason continuous transmission is needed, the `refreshmode` can be set to `always`, opposed to the default `standard`.
### Chaser Thing (`chaser`)
There are two mandatory configuration values for a chaser thing: the `dmxid` and `steps`.
The `dmxid` is a list of DMX channels that are associated with this thing.
There are several possible formats: `channel,channel,channel,...` or `channel/width` or a combination of both.
The `steps` value is a list of steps that shall be run by the chaser.
The format of a single step is `fadetime:value,value2, ...:holdtime`, two or more steps are concatenated by `step1|step2|...`.
In textual configuration line-breaks, spaces and tabs are allowed for readability.
The fadetime is used for fading from the current value to the new value.
In contrast to the dimmer thing, this is an absolute value.
The hold time defines how long this step shall wait before advancing to the next step.
A value of -1 is used to hold forever.
Both times are in ms.
An optional configuration value is `resumeafter`.
It defaults to false but if set to true, the original state of the channel (including running fades) will be suspended until the chaser receives an OFF command.
### Dimmer Thing (`dimmer`)
There is one mandatory configuration value for a dimmer thing.
It is the `dmxid`, a list of DMX channels that are associated with this thing.
There are several possible formats: `channel1,channel2,channel3,...` or `channel/width` or a combination of both.
The `fadetime` option allows a smooth transition from the current to the new value.
The time unit is ms and the interval is for a fade from 0-100%.
If the current value is 25% and the new value is 75% the time needed for this change is half of `fadetime`.
`fadetime`is used for absolute values or ON/OFF commands send to the `brightness` channel.
Related is the `dimtime` option: it defines the time in ms from 0-100% if incremental dimming (`INCREASE`/`DECREASE`) is used.
For convenient use `dimtime` usually is set to a larger value than `fadetime`.
Typical values are 500-1000 ms for `fadetime` and 2000-5000 ms for `dimtime`.
Advanced options are the `turnonvalue`and the `turnoffvalue`.
They default to 255 (equals 100%) and 0 (equals 0%) respectively.
This value can be set individually for all DMX channels, the format is `value1,value2, ...` with values from 0 to 255.
If less values than DMX channels are defined, the values will be re-used from the beginning (i.e. if two values are defined, value1 will be used for channel1, channel3, ... and value2 will be used for channel2, channel4, ...).
These values will be used if the thing receives an ON or OFF command.
The `dynamicturnonvalue` can be set to `true` or `false` (default).
If enabled, thing overwrites the previous turn-on value with the current channel values.
The next `ON` command uses these values instead of the default (or configuration supplied) values.
### Color Thing (`color`)
There is one mandatory configuration value for a dimmer thing.
It is the `dmxid`, a list of DMX channels that are associated with this thing.
There are several possible formats: `channel1,channel2,channel3,...` or `channel/width` or a combination of both.
The number of channels has to be a multiple of three.
The `fadetime` option allows a smooth transition from the current to the new value.
The time unit is ms and the interval is for a fade from 0-100%.
If the current value is 25% and the new value is 75% the time needed for this change is half of `fadetime`.
`fadetime`is used for absolute values or ON/OFF commands send to the `brightness` channel.
Related is the `dimtime` option: it defines the time in ms from 0-100% if incremental dimming (`INCREASE`/`DECREASE`) is used.
For convenient use `dimtime` usually is set to a larger value than `fadetime`.
Typical values are 500-1000 ms for `fadetime` and 2000-5000 ms for `dimtime`.
Advanced options are the `turnonvalue`and the `turnoffvalue`.
They default to 255 (equals 100%) and 0 (equals 0%) respectively.
This value can be set individually for all DMX channels, the format is `value1,value2, ...` with values from 0 to 255.
If less values than DMX channels are defined, the values will be re-used from the beginning (i.e. if two values are defined, value1 will be used for channel1, channel3, ... and value2 will be used for channel2, channel4, ...).
For color things the number of values has to be a multiple of three.
These values will be used if the thing receives an ON or OFF command.
The `dynamicturnonvalue` can be set to `true` or `false` (default).
If enabled, thing overwrites the previous turn-on value with the current channel values.
The next `ON` command uses these values instead of the default (or configuration supplied) values.
### Tunable White Thing (`tunablewhite`)
There is one mandatory configuration value for a dimmer thing.
It is the `dmxid`, a list of DMX channels that are associated with this thing.
There are several possible formats: `channel1,channel2,channel3,...` or `channel/width` or a combination of both.
The number of channels has to be even. In the order "cool white, warm white".
Additionally a channel for cool and warm white brightness as well as color temperature (`0` being the coolest, `100` being the warmest) will be provided.
The `fadetime` option allows a smooth transition from the current to the new value.
The time unit is ms and the interval is for a fade from 0-100%.
If the current value is 25% and the new value is 75% the time needed for this change is half of `fadetime`.
`fadetime`is used for absolute values or ON/OFF commands send to the `brightness` channel.
Related is the `dimtime` option: it defines the time in ms from 0-100% if incremental dimming (`INCREASE`/`DECREASE`) is used.
For convenient use `dimtime` usually is set to a larger value than `fadetime`.
Typical values are 500-1000 ms for `fadetime` and 2000-5000 ms for `dimtime`.
Advanced options are the `turnonvalue`and the `turnoffvalue`.
They default to 255 (equals 100%) and 0 (equals 0%) respectively.
This value can be set individually for all DMX channels, the format is `value1,value2, ...` with values from 0 to 255.
If less values than DMX channels are defined, the values will be re-used from the beginning (i.e. if two values are defined, value1 will be used for channel1, channel3, ... and value2 will be used for channel2, channel4, ...).
For tunable white things the number of values has to be a multiple of two.
These values will be used if the thing receives an ON or OFF command.
The `dynamicturnonvalue` can be set to `true` or `false` (default).
If enabled, thing overwrites the previous turn-on value with the current channel values.
The next `ON` command uses these values instead of the default (or configuration supplied) values.
## Channels
| Type-ID | Thing | Item | Description |
|-----------------|---------------------|----------------------|----------------------------------------------------|
|brightness |dimmer, tunablewhite |Switch, Dimmer | controls the brightness |
|color |color |Switch, Dimmer, Color | allows to set the color and brightness |
|color_temperature|tunablewhite |Number | allows to set the color temperature |
|brightness_r |color |Switch, Dimmer | controls the brightness of the red channel |
|brightness_g |color |Switch, Dimmer | controls the brightness of the green channel |
|brightness_b |color |Switch, Dimmer | controls the brightness of the blue channel |
|brightness_cw |tunablewhite |Switch, Dimmer | controls the brightness of the cool white channel |
|brightness_ww |tunablewhite |Switch, Dimmer | controls the brightness of the warm white channel |
|control |chaser |String | allows to change the chaser steps |
|switch |chaser |Switch | turns the chaser ON or OFF |
|mute |(all bridges) |Switch | mutes the DMX output of the bridge |
*Note:* the string send to the control channel of chaser things has to be formatted like the `steps` configuration of the chaser thing.
If the new string is invalid, the old configuration will be used.
## Rule Actions
This binding includes a rule action, which allows to immediately change DMX channels from within rules.
There is a separate instance for each bridge, which can be retrieved e.g. through
```
val dmxActions = getActions("dmx","dmx:sacn-bridge:mydmxbridge")
```
where the first parameter always has to be `dmx` and the second is the full Thing UID of the bridge that should be used.
Once this action instance is retrieved, you can invoke the `sendFade(String channels, String fade, Boolean resumeAfter)` method on it:
```
dmxActions.sendFade("1:41/3","10000:255,255,255:-1", false)
```
The parameters are the same as in a chaser thing configuration.
Defining more than one step in `fadeString` is supported, too.
## Full Example
This example defines a sACN/E1.31 bridge in unicast mode which transmits universe 2 and three things: a three channel dimmer used to control a RGB light, which takes 1s to fade from one color to another and 10s from 0-100% on incremental dim commands, a single channel dimmer which will turn on only to 90% if it receives an ON command and does not fully switch off (to 10%) if it receives an OFF command and chaser which changes the colors like a traffic light.
### demo.things:
```
Bridge dmx:sacn-bridge:mybridge [ mode="unicast", address="192.168.0.60", universe=2 ] {
color rgb [dmxid="5/3", fadetime=1000, dimtime=10000 ]
dimmer single [dmxid="50", fadetime=1000, turnonvalue="230", turnoffvalue="25" ]
chaser ampel [dmxid="10,12,13", steps="100:255,0,0:1000|100:255,255,0:500|100:0,0,255:1000|100:0,255,0:500" ]
}
```
### demo.items:
```
Color MyColorItem "My Color Item" { channel="dmx:color:mybridge:rgb:color" }
Dimmer MyDimmerItem "My Dimmer Item" { channel="dmx:dimmer:mybridge:single:brightness" }
Switch MyChaserItem "My Chaser Item" { channel="dmx:chaser:mybridge:ampel:switch" }
```
### demo.sitemap:
```
sitemap demo label="Main Menu"
{
Frame {
// Color
Colorpicker item=MyColorItem
// Dimmer
Switch item=MyDimmerItem
Slider item=MyDimmerItem
// Chaser
Switch item=MyChaserItem
}
}
```

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.dmx</artifactId>
<name>openHAB Add-ons :: Bundles :: DMX Binding</name>
</project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.dmx-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-dmx" description="DMX Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.dmx/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,113 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.action;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.dmx.internal.DmxBridgeHandler;
import org.openhab.core.automation.annotation.ActionInput;
import org.openhab.core.automation.annotation.RuleAction;
import org.openhab.core.thing.binding.ThingActions;
import org.openhab.core.thing.binding.ThingActionsScope;
import org.openhab.core.thing.binding.ThingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link DmxActions} provides actions for DMX Bridges
* <p>
* <b>Note:</b>The static method <b>invokeMethodOf</b> handles the case where
* the test <i>actions instanceof DmxActions</i> fails. This test can fail
* due to an issue in openHAB core v2.5.0 where the {@link DmxActions} class
* can be loaded by a different classloader than the <i>actions</i> instance.
*
* @author Jan N. Klug - Initial contribution
*/
@ThingActionsScope(name = "dmx")
@NonNullByDefault
public class DmxActions implements ThingActions, IDmxActions {
private final Logger logger = LoggerFactory.getLogger(DmxActions.class);
private @Nullable DmxBridgeHandler handler;
@Override
@RuleAction(label = "DMX Output", description = "immediately performs fade on selected DMX channels")
public void sendFade(@ActionInput(name = "channels") @Nullable String channels,
@ActionInput(name = "fade") @Nullable String fade,
@ActionInput(name = "resumeAfter") @Nullable Boolean resumeAfter) {
logger.debug("thingHandlerAction called with inputs: {} {} {}", channels, fade, resumeAfter);
if (handler == null) {
logger.warn("DMX Action service ThingHandler is null!");
return;
}
if (channels == null) {
logger.debug("skipping immediate DMX action {} due to missing channel(s)", fade);
return;
}
if (fade == null) {
logger.debug("skipping immediate DMX action channel(s) {} due to missing fade", channels);
return;
}
if (resumeAfter == null) {
logger.debug("DMX action {} to channel(s) {} with default resumeAfter=false", fade, channels);
handler.immediateFade(channels, fade, false);
} else {
handler.immediateFade(channels, fade, resumeAfter);
}
}
public static void sendFade(@Nullable ThingActions actions, @Nullable String channels, @Nullable String fade,
@Nullable Boolean resumeAfter) {
invokeMethodOf(actions).sendFade(channels, fade, resumeAfter);
}
private static IDmxActions invokeMethodOf(@Nullable ThingActions actions) {
if (actions == null) {
throw new IllegalArgumentException("actions cannot be null");
}
if (actions.getClass().getName().equals(DmxActions.class.getName())) {
if (actions instanceof IDmxActions) {
return (IDmxActions) actions;
} else {
return (IDmxActions) Proxy.newProxyInstance(IDmxActions.class.getClassLoader(),
new Class[] { IDmxActions.class }, (Object proxy, Method method, Object[] args) -> {
Method m = actions.getClass().getDeclaredMethod(method.getName(),
method.getParameterTypes());
return m.invoke(actions, args);
});
}
}
throw new IllegalArgumentException("Actions is not an instance of DmxActions");
}
@Override
public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof DmxBridgeHandler) {
this.handler = (DmxBridgeHandler) handler;
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return this.handler;
}
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.action;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link IDmxActions} defines the actions for DMX Bridges
*
* @author Laurent Garnier - Initial contribution
*/
@NonNullByDefault
public interface IDmxActions {
public void sendFade(@Nullable String channels, @Nullable String fade, @Nullable Boolean resumeAfter);
}

View File

@@ -0,0 +1,94 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.type.ChannelTypeUID;
/**
* The {@link DmxBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Jan N. Klug - Initial contribution
*/
public class DmxBindingConstants {
public static final String BINDING_ID = "dmx";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_CHASER = new ThingTypeUID(BINDING_ID, "chaser");
public static final ThingTypeUID THING_TYPE_DIMMER = new ThingTypeUID(BINDING_ID, "dimmer");
public static final ThingTypeUID THING_TYPE_COLOR = new ThingTypeUID(BINDING_ID, "color");
public static final ThingTypeUID THING_TYPE_TUNABLEWHITE = new ThingTypeUID(BINDING_ID, "tunablewhite");
public static final ThingTypeUID THING_TYPE_ARTNET_BRIDGE = new ThingTypeUID(BINDING_ID, "artnet-bridge");
public static final ThingTypeUID THING_TYPE_LIB485_BRIDGE = new ThingTypeUID(BINDING_ID, "lib485-bridge");
public static final ThingTypeUID THING_TYPE_SACN_BRIDGE = new ThingTypeUID(BINDING_ID, "sacn-bridge");
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.unmodifiableSet(
Stream.of(THING_TYPE_ARTNET_BRIDGE, THING_TYPE_LIB485_BRIDGE, THING_TYPE_SACN_BRIDGE, THING_TYPE_CHASER,
THING_TYPE_COLOR, THING_TYPE_DIMMER, THING_TYPE_TUNABLEWHITE).collect(Collectors.toSet()));
// List of all config options
public static final String CONFIG_UNIVERSE = "universe";
public static final String CONFIG_DMX_ID = "dmxid";
public static final String CONFIG_APPLY_CURVE = "applycurve";
public static final String CONFIG_REFRESH_RATE = "refreshrate";
public static final String CONFIG_SACN_MODE = "mode";
public static final String CONFIG_ADDRESS = "address";
public static final String CONFIG_LOCAL_ADDRESS = "localaddress";
public static final String CONFIG_REFRESH_MODE = "refreshmode";
public static final String CONFIG_DIMMER_TYPE = "dimmertype";
public static final String CONFIG_DIMMER_FADE_TIME = "fadetime";
public static final String CONFIG_DIMMER_DIM_TIME = "dimtime";
public static final String CONFIG_DIMMER_TURNONVALUE = "turnonvalue";
public static final String CONFIG_DIMMER_TURNOFFVALUE = "turnoffvalue";
public static final String CONFIG_DIMMER_DYNAMICTURNONVALUE = "dynamicturnonvalue";
public static final String CONFIG_CHASER_STEPS = "steps";
public static final String CONFIG_CHASER_RESUME_AFTER = "resumeafter";
// List of all channels
public static final String CHANNEL_BRIGHTNESS = "brightness";
public static final String CHANNEL_BRIGHTNESS_R = "brightness_r";
public static final String CHANNEL_BRIGHTNESS_G = "brightness_g";
public static final String CHANNEL_BRIGHTNESS_B = "brightness_b";
public static final String CHANNEL_BRIGHTNESS_CW = "brightness_cw";
public static final String CHANNEL_BRIGHTNESS_WW = "brightness_ww";
public static final String CHANNEL_COLOR = "color";
public static final String CHANNEL_COLOR_TEMPERATURE = "color_temperature";
public static final String CHANNEL_SWITCH = "switch";
public static final String CHANNEL_CONTROL = "control";
public static final String CHANNEL_MUTE = "mute";
public static final ChannelTypeUID BRIGHTNESS_CHANNEL_TYPEUID = new ChannelTypeUID(BINDING_ID, CHANNEL_BRIGHTNESS);
public static final ChannelTypeUID COLOR_CHANNEL_TYPEUID = new ChannelTypeUID(BINDING_ID, CHANNEL_COLOR);
public static final ChannelTypeUID COLOR_TEMPERATURE_CHANNEL_TYPEUID = new ChannelTypeUID(BINDING_ID,
CHANNEL_COLOR_TEMPERATURE);
public static final ChannelTypeUID SWITCH_CHANNEL_TYPEUID = new ChannelTypeUID(BINDING_ID, CHANNEL_SWITCH);
public static final ChannelTypeUID CONTROL_CHANNEL_TYPEUID = new ChannelTypeUID(BINDING_ID, CHANNEL_CONTROL);
public static final ChannelTypeUID MUTE_CHANNEL_TYPEUID = new ChannelTypeUID(BINDING_ID, CHANNEL_MUTE);
// Listener Type for channel updates
public static enum ListenerType {
VALUE,
ACTION
}
}

View File

@@ -0,0 +1,289 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal;
import static org.openhab.binding.dmx.internal.DmxBindingConstants.CHANNEL_MUTE;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.dmx.action.DmxActions;
import org.openhab.binding.dmx.internal.action.FadeAction;
import org.openhab.binding.dmx.internal.action.ResumeAction;
import org.openhab.binding.dmx.internal.config.DmxBridgeHandlerConfiguration;
import org.openhab.binding.dmx.internal.multiverse.BaseDmxChannel;
import org.openhab.binding.dmx.internal.multiverse.DmxChannel;
import org.openhab.binding.dmx.internal.multiverse.Universe;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link DmxBridgeHandler} is an abstract class with base functions
* for DMX Bridges
*
* @author Jan N. Klug - Initial contribution
*/
public abstract class DmxBridgeHandler extends BaseBridgeHandler {
public static final int DEFAULT_REFRESH_RATE = 20;
private final Logger logger = LoggerFactory.getLogger(DmxBridgeHandler.class);
protected Universe universe;
private ScheduledFuture<?> senderJob;
private boolean isMuted = false;
private int refreshTime = 1000 / DEFAULT_REFRESH_RATE;
public DmxBridgeHandler(Bridge dmxBridge) {
super(dmxBridge);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
switch (channelUID.getId()) {
case CHANNEL_MUTE:
if (command instanceof OnOffType) {
isMuted = ((OnOffType) command).equals(OnOffType.ON);
} else {
logger.debug("command {} not supported in channel {}:mute", command.getClass(),
this.thing.getUID());
}
break;
default:
logger.warn("Channel {} not supported in bridge {}", channelUID.getId(), this.thing.getUID());
}
}
/**
* get a DMX channel from the bridge
*
* @param channel a BaseChannel that identifies the requested channel
* @param thing the Thing that requests the channel to track channel usage
* @return a Channel object
*/
public DmxChannel getDmxChannel(BaseDmxChannel channel, Thing thing) {
return universe.registerChannel(channel, thing);
}
/**
* remove a thing from all channels in the universe
*
* @param thing the thing that shall be removed
*/
public void unregisterDmxChannels(Thing thing) {
universe.unregisterChannels(thing);
}
/**
* get the universe associated with this bridge
*
* @return the DMX universe id
*/
public int getUniverseId() {
return universe.getUniverseId();
}
/**
* rename the universe associated with this bridge
*
* @param universeId the new DMX universe id
*/
protected void renameUniverse(int universeId) {
universe.rename(universeId);
}
@Override
public void thingUpdated(Thing thing) {
updateConfiguration();
}
/**
* open the connection to send DMX data to
*/
protected abstract void openConnection();
/**
* close the connection to send DMX data to
*/
protected abstract void closeConnection();
/**
* close the connection to send DMX data and update thing Status
*
* @param statusDetail ThingStatusDetail for thingStatus OFFLINE
* @param description string giving the reason for closing the connection
*/
protected void closeConnection(ThingStatusDetail statusDetail, String description) {
updateStatus(ThingStatus.OFFLINE, statusDetail, description);
closeConnection();
}
/**
* send the buffer of the current universe
*/
protected abstract void sendDmxData();
/**
* install the sending and updating scheduler
*/
protected void installScheduler() {
if (senderJob != null) {
uninstallScheduler();
}
if (refreshTime > 0) {
senderJob = scheduler.scheduleAtFixedRate(() -> {
logger.trace("runnable packet sender for universe {} called, state {}/{}", universe.getUniverseId(),
getThing().getStatus(), isMuted);
if (!isMuted) {
sendDmxData();
} else {
logger.trace("bridge {} is muted", getThing().getUID());
}
}, 1, refreshTime, TimeUnit.MILLISECONDS);
logger.trace("started scheduler for thing {}", this.thing.getUID());
} else {
logger.info("refresh disabled for thing {}", this.thing.getUID());
}
}
/**
* uninstall the sending and updating scheduler
*/
protected void uninstallScheduler() {
if (senderJob != null) {
if (!senderJob.isCancelled()) {
senderJob.cancel(true);
}
senderJob = null;
closeConnection();
logger.trace("stopping scheduler for thing {}", this.thing.getUID());
}
}
@Override
public void childHandlerDisposed(ThingHandler thingHandler, Thing thing) {
universe.unregisterChannels(thing);
}
/**
* get the configuration and update the bridge
*/
protected void updateConfiguration() {
DmxBridgeHandlerConfiguration configuration = getConfig().as(DmxBridgeHandlerConfiguration.class);
if (!configuration.applycurve.isEmpty()) {
universe.setDimCurveChannels(configuration.applycurve);
}
int refreshRate = configuration.refreshrate;
if (refreshRate > 0) {
refreshTime = (int) (1000.0 / refreshRate);
} else {
refreshTime = 0;
}
logger.debug("set refreshTime to {} ms in thing {}", refreshTime, this.thing.getUID());
installScheduler();
}
@Override
public void dispose() {
uninstallScheduler();
}
/**
* set the universe id and make sure it observes the limits
*
* @param universeConfig ConfigurationObject
* @param minUniverseId the minimum id allowed by the bridge
* @param maxUniverseId the maximum id allowed by the bridge
**/
protected void setUniverse(int universeConfig, int minUniverseId, int maxUniverseId) {
int universeId = minUniverseId;
universeId = Util.coerceToRange(universeConfig, minUniverseId, maxUniverseId, logger, "universeId");
if (universe == null) {
universe = new Universe(universeId);
} else if (universe.getUniverseId() != universeId) {
universe.rename(universeId);
}
}
/**
* sends an immediate fade to the DMX output (for rule actions)
*
* @param channelString a String containing the channels
* @param fadeString a String containing the fade/chase definition
* @param resumeAfter a boolean if the previous state should be restored
*/
public void immediateFade(String channelString, String fadeString, Boolean resumeAfter) {
// parse channel config
List<DmxChannel> channels = new ArrayList<>();
try {
List<BaseDmxChannel> configChannels = BaseDmxChannel.fromString(channelString, getUniverseId());
logger.trace("found {} channels in {}", configChannels.size(), this.thing.getUID());
for (BaseDmxChannel channel : configChannels) {
channels.add(getDmxChannel(channel, this.thing));
}
} catch (IllegalArgumentException e) {
logger.warn("invalid channel configuration: {}", channelString);
return;
}
// parse fade config
List<ValueSet> value = ValueSet.parseChaseConfig(fadeString);
if (value.isEmpty()) {
logger.warn("invalid fade configuration: {}", fadeString);
return;
}
// do action
Integer channelCounter = 0;
for (DmxChannel channel : channels) {
if (resumeAfter) {
channel.suspendAction();
} else {
channel.clearAction();
}
for (ValueSet step : value) {
channel.addChannelAction(
new FadeAction(step.getFadeTime(), step.getValue(channelCounter), step.getHoldTime()));
}
if (resumeAfter) {
channel.addChannelAction(new ResumeAction());
}
channelCounter++;
}
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singletonList(DmxActions.class);
}
}

View File

@@ -0,0 +1,73 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal;
import static org.openhab.binding.dmx.internal.DmxBindingConstants.*;
import org.openhab.binding.dmx.internal.handler.ArtnetBridgeHandler;
import org.openhab.binding.dmx.internal.handler.ChaserThingHandler;
import org.openhab.binding.dmx.internal.handler.ColorThingHandler;
import org.openhab.binding.dmx.internal.handler.DimmerThingHandler;
import org.openhab.binding.dmx.internal.handler.Lib485BridgeHandler;
import org.openhab.binding.dmx.internal.handler.SacnBridgeHandler;
import org.openhab.binding.dmx.internal.handler.TunableWhiteThingHandler;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Component;
/**
* The {@link DmxHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Jan N. Klug - Initial contribution
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.dmx")
public class DmxHandlerFactory extends BaseThingHandlerFactory {
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return DmxBindingConstants.SUPPORTED_THING_TYPES.contains(thingTypeUID);
}
@Override
protected ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(THING_TYPE_ARTNET_BRIDGE)) {
ArtnetBridgeHandler handler = new ArtnetBridgeHandler((Bridge) thing);
return handler;
} else if (thingTypeUID.equals(THING_TYPE_LIB485_BRIDGE)) {
Lib485BridgeHandler handler = new Lib485BridgeHandler((Bridge) thing);
return handler;
} else if (thingTypeUID.equals(THING_TYPE_SACN_BRIDGE)) {
SacnBridgeHandler handler = new SacnBridgeHandler((Bridge) thing);
return handler;
} else if (thingTypeUID.equals(THING_TYPE_DIMMER)) {
DimmerThingHandler handler = new DimmerThingHandler(thing);
return handler;
} else if (thingTypeUID.equals(THING_TYPE_COLOR)) {
ColorThingHandler handler = new ColorThingHandler(thing);
return handler;
} else if (thingTypeUID.equals(THING_TYPE_TUNABLEWHITE)) {
TunableWhiteThingHandler handler = new TunableWhiteThingHandler(thing);
return handler;
} else if (thingTypeUID.equals(THING_TYPE_CHASER)) {
ChaserThingHandler handler = new ChaserThingHandler(thing);
return handler;
}
return null;
}
}

View File

@@ -0,0 +1,70 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.State;
/**
* The {@link DmxThingHandler} is an abstract class with base functions
* for DMX Things
*
* @author Jan N. Klug - Initial contribution
*/
public abstract class DmxThingHandler extends BaseThingHandler {
protected ThingStatusDetail dmxHandlerStatus = ThingStatusDetail.HANDLER_CONFIGURATION_PENDING;
public DmxThingHandler(Thing thing) {
super(thing);
}
/**
* updates the switch state (if any)
*
* @param channelUID channelUID provided in channel registration
* @param state (ON / OFF)
*/
public void updateSwitchState(ChannelUID channelUID, State state) {
updateState(channelUID, state);
}
/**
* updates the internal values from the DMX channels
*
* @param channelUID channelUID provided in channel registration
* @param value (0-255)
*/
public void updateChannelValue(ChannelUID channelUID, int value) {
}
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
super.bridgeStatusChanged(bridgeStatusInfo);
if (ThingStatus.ONLINE.equals(bridgeStatusInfo.getStatus())
&& ThingStatus.OFFLINE.equals(getThing().getStatusInfo().getStatus())
&& ThingStatusDetail.BRIDGE_OFFLINE.equals(getThing().getStatusInfo().getStatusDetail())) {
if (ThingStatusDetail.NONE.equals(dmxHandlerStatus)) {
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
} else {
updateStatus(ThingStatus.OFFLINE, dmxHandlerStatus);
}
}
}
}

View File

@@ -0,0 +1,166 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal;
import java.math.BigDecimal;
import org.openhab.binding.dmx.internal.multiverse.DmxChannel;
import org.openhab.core.library.types.PercentType;
import org.slf4j.Logger;
/**
* {@link Util} is a set of helper functions
*
* @author Jan N. Klug - Initial contribution
*/
public class Util {
/**
* inRange checks if a value is between two other values
*
* @param value test value
* @param min
* @param max
* @return true or false
*/
public static boolean inRange(int value, int min, int max) {
if (value < min) {
return false;
}
if (value > max) {
return false;
}
return true;
}
/**
* coerce a value to fit in a range, write log with comment
*
* @param value
* @param min
* @param max
* @param logger logger that shall be used
* @param var name of the variable (used for logging)
* @return coerced value
*/
public static int coerceToRange(int value, int min, int max, Logger logger, String var) {
if (value < min) {
if (logger != null) {
logger.warn("coerced {} {} to allowed range {}-{}", var, value, min, max);
}
return min;
}
if (value > max) {
if (logger != null) {
logger.warn("coerced {} {} to allowed range {}-{}", var, value, min, max);
}
return max;
}
return value;
}
/**
* coerce a value to fit in a range, write log
*
* @param value
* @param min
* @param max
* @param logger logger that shall be used
* @return coerced value
*/
public static int coerceToRange(int value, int min, int max, Logger logger) {
return coerceToRange(value, min, max, logger, "");
}
/**
* coerce a value to fit in a range
*
* @param value
* @param min
* @param max
* @return coerced value
*/
public static int coerceToRange(int value, int min, int max) {
return coerceToRange(value, min, max, null, "");
}
/**
* convert PercentType to DMX value and check range
*
* @param value value as PercentType
* @return value as Integer (0-255)
*/
public static int toDmxValue(PercentType value) {
int intValue = (int) (value.doubleValue() * (DmxChannel.MAX_VALUE - DmxChannel.MIN_VALUE) / 100.0
+ DmxChannel.MIN_VALUE);
return coerceToRange(intValue, DmxChannel.MIN_VALUE, DmxChannel.MAX_VALUE);
}
/**
* check range of DMX value
*
* @param value value as Integer
* @return value as Integer (0-255)
*/
public static int toDmxValue(int value) {
return coerceToRange(value, DmxChannel.MIN_VALUE, DmxChannel.MAX_VALUE);
}
/**
* check range of DMX value
*
* @param value value as String
* @return value as Integer (0-255)
*/
public static int toDmxValue(String value) {
return coerceToRange(Integer.valueOf(value), DmxChannel.MIN_VALUE, DmxChannel.MAX_VALUE);
}
/**
* convert float to DMX value
*
* @param value value as float
* @return value as Integer (0-255)
*/
public static int toDmxValue(float value) {
return toDmxValue(Math.round(value));
}
/**
* convert DMX value to PercentType
*
* @param value value as Integer( 0-255)
* @return value as PercentType
*/
public static PercentType toPercentValue(int value) {
if (value == DmxChannel.MIN_VALUE) {
return PercentType.ZERO;
} else {
return new PercentType(new BigDecimal(
((value - DmxChannel.MIN_VALUE) * 100.0) / (DmxChannel.MAX_VALUE - DmxChannel.MIN_VALUE)));
}
}
/**
* calculate a fraction of the fadeTime depending on current and target value
*
* @param currentValue current channel value as Integer
* @param targetValue target channel value as Integer
* @param fadeTime fadeTime in ms
* @return fraction needed for fading
*/
public static int fadeTimeFraction(int currentValue, int targetValue, int fadeTime) {
return Math.abs(targetValue - currentValue) * fadeTime / (DmxChannel.MAX_VALUE - DmxChannel.MIN_VALUE);
}
}

View File

@@ -0,0 +1,198 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openhab.core.library.types.PercentType;
/**
* The {@link ValueSet} holds a set of values and fade times
*
* @author Jan N. Klug - Initial contribution
*/
public class ValueSet {
protected static final Pattern VALUESET_PATTERN = Pattern.compile("^(\\d*):([\\d,]*):([\\d-]*)$");
private int fadeTime;
private int holdTime;
private final List<Integer> values = new ArrayList<>();
/**
* constructor with fade times only
*
* @param fadeTime fade time in ms
* @param holdTime hold time in ms, -1 is forever
*/
public ValueSet(int fadeTime, int holdTime) {
this.fadeTime = fadeTime;
this.holdTime = holdTime;
}
/**
* constructor with fade times and value
*
* @param fadeTime fade time in ms
* @param holdTime hold time in ms, -1 is forever
* @param value DMX value (0-255)
*/
public ValueSet(int fadeTime, int holdTime, int value) {
this.fadeTime = fadeTime;
this.holdTime = holdTime;
addValue(value);
}
/**
* set fade time
*
* @param fadeTime fade time in ms
*/
public void setFadeTime(int fadeTime) {
this.fadeTime = fadeTime;
}
/**
* get fade time
*
* @return fade time in ms
*/
public int getFadeTime() {
return fadeTime;
}
/**
* set hold time
*
* @param holdTime fade time in ms
*/
public void setHoldTime(int holdTime) {
this.holdTime = holdTime;
}
/**
* get hold time
*
* @return hold time in ms (-1 = forever)
*/
public int getHoldTime() {
return holdTime;
}
/**
* add a value to this list
*
* @param value value (0-255)
*/
public void addValue(int value) {
values.add(Util.toDmxValue(value));
}
/**
* add a value to this list
*
* @param value value (0-100%)
*/
public void addValue(PercentType value) {
values.add(Util.toDmxValue(value));
}
/**
* get a value from this value set
*
* @param index index of value, if larger than list, re-use from start
* @return value in the range of 0-255
*/
public int getValue(int index) {
return values.get(index % values.size());
}
/**
* returns true if this value set contains no elements
*
* @return true if empty
*/
public boolean isEmpty() {
return values.isEmpty();
}
/**
* returns the number of elements in this value set
*
* @return number of elements
*/
public int size() {
return values.size();
}
/**
* remove all elements from ValueSet
*/
public void clear() {
values.clear();
}
/**
* parse a value set from a string
*
* @param valueSetConfig a string holding a complete value set configuration fadeTime:value,value2,...:holdTime
* @return a new ValueSet
*/
public static ValueSet fromString(String valueSetConfig) {
Matcher valueSetMatch = VALUESET_PATTERN.matcher(valueSetConfig);
if (valueSetMatch.matches()) {
ValueSet step = new ValueSet(Integer.valueOf(valueSetMatch.group(1)),
Integer.valueOf(valueSetMatch.group(3)));
for (String value : valueSetMatch.group(2).split(",")) {
step.addValue(Integer.valueOf(value));
}
return step;
} else {
return new ValueSet(0, -1);
}
}
/**
* parses a chase configuration (list of value sets) from a string
*
* @param chaseConfigString a string containing the chaser definition
* @return a list of ValueSets containing the chase configuration
*/
public static List<ValueSet> parseChaseConfig(String chaseConfigString) {
List<ValueSet> chaseConfig = new ArrayList<>();
String strippedConfig = chaseConfigString.replaceAll("(\\s)+", "");
for (String singleStepString : strippedConfig.split("\\|")) {
ValueSet value = ValueSet.fromString(singleStepString);
if (!value.isEmpty()) {
chaseConfig.add(value);
} else {
chaseConfig.clear();
return chaseConfig;
}
}
return chaseConfig;
}
@Override
public String toString() {
String str = "fade/hold:" + String.valueOf(fadeTime) + "/" + String.valueOf(holdTime) + ": ";
for (Integer value : values) {
str += String.valueOf(value) + " ";
}
return str;
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.action;
/**
* The {@link ActionState} gives the state of an action
*
* waiting : not started yet
* running : action is running
* completed : action has completed, proceed to next action
* completedfinal : action has completed, hold here
*
* @author Jan N. Klug - Initial contribution
*/
public enum ActionState {
WAITING,
RUNNING,
COMPLETED,
COMPLETEDFINAL
}

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.action;
import org.openhab.binding.dmx.internal.multiverse.DmxChannel;
/**
* The {@link BaseAction} is the base class for Actions like faders, chasers, etc..
*
* @author Davy Vanherbergen - Initial contribution
* @author Jan N. Klug - Refactoring for ESH
*/
public abstract class BaseAction {
protected ActionState state = ActionState.WAITING;
protected long startTime = 0;
/**
* Calculate the new output value of the channel.
*
* @param channel
* @param currentTime UNIX timestamp to use as current time
* @return value as float between 0 - 65535
*/
public abstract int getNewValue(DmxChannel channel, long currentTime);
/**
* @return the action's state
*/
public final ActionState getState() {
return state;
}
/**
* Reset the action to start from the beginning.
*/
public void reset() {
startTime = 0;
state = ActionState.WAITING;
}
}

View File

@@ -0,0 +1,145 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.action;
import org.openhab.binding.dmx.internal.Util;
import org.openhab.binding.dmx.internal.multiverse.DmxChannel;
import org.openhab.core.library.types.PercentType;
/**
* The {@link FadeAction} fades a given channel from its current state to the requested
* state in the given amount of time. After the fade, the new state is held for
* a given or indefinite time.
*
* @author Davy Vanherbergen - Initial contribution
* @author Jan N. Klug - Refactoring for ESH
*/
public class FadeAction extends BaseAction {
/** Time in ms to hold the target value. -1 is indefinite */
private long holdTime;
/** Time in ms to fade from current value to new target value */
private long fadeTime;
/** Channel output value on action start. **/
private int startValue;
/** Desired channel output value. **/
private final int targetValue;
private float stepDuration;
private FadeDirection fadeDirection;
/**
* Create new fading action.
*
* @param fadeTime time in ms to fade from the current value to the new value.
* @param targetValue new value 0-255 for this channel.
* @param holdTime time in ms to hold the color before moving to the next action. -1 is indefinite.
*/
public FadeAction(int fadeTime, int targetValue, int holdTime) {
super();
this.fadeTime = fadeTime;
this.targetValue = Util.toDmxValue(targetValue) << 8;
this.holdTime = holdTime;
if (holdTime < -1) {
this.holdTime = -1;
}
if (fadeTime < 0) {
this.fadeTime = 0;
}
}
public FadeAction(int fadeTime, PercentType targetValue, int holdTime) {
this(fadeTime, Util.toDmxValue(targetValue), holdTime);
}
public FadeAction(int fadeTime, int currentValue, int targetValue, int holdTime) {
this(Util.fadeTimeFraction(currentValue, targetValue, fadeTime), targetValue, holdTime);
}
@Override
public int getNewValue(DmxChannel channel, long currentTime) {
int newValue = channel.getHiResValue();
if (startTime == 0) {
startTime = currentTime;
state = ActionState.RUNNING;
if (fadeTime != 0) {
startValue = channel.getHiResValue();
// calculate fade details
if (startValue == targetValue) {
stepDuration = 1;
} else if (startValue > targetValue) {
fadeDirection = FadeDirection.DOWN;
stepDuration = (float) fadeTime / (startValue - targetValue);
} else {
fadeDirection = FadeDirection.UP;
stepDuration = (float) fadeTime / (targetValue - startValue);
}
} else {
newValue = targetValue;
}
}
long duration = currentTime - startTime;
if ((fadeTime != 0) && (newValue != targetValue)) {
// calculate new fade value
if (stepDuration == 0) {
stepDuration = 1;
}
int currentStep = (int) (duration / stepDuration);
if (fadeDirection == FadeDirection.UP) {
newValue = startValue + currentStep;
if (newValue > targetValue) {
newValue = targetValue;
}
} else {
newValue = startValue - currentStep;
if (newValue < targetValue) {
newValue = targetValue;
}
}
} else {
newValue = targetValue;
}
if (newValue == targetValue) {
if (holdTime > -1) {
// we reached the target already, check if we need to hold longer
if (((holdTime > 0 || fadeTime > 0) && (duration >= fadeTime + holdTime))
|| (holdTime == 0 && fadeTime == 0)) {
// mark action as completed
state = ActionState.COMPLETED;
}
} else {
state = ActionState.COMPLETEDFINAL;
}
}
return newValue;
}
@Override
public String toString() {
return "FadeAction: " + String.valueOf(targetValue) + ", fade time " + String.valueOf(fadeTime)
+ "ms, hold time " + String.valueOf(holdTime) + "ms";
}
}

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.action;
/**
* The {@link FadeDirection} gives the direction for fading
*
* @author Jan N. Klug - Initial contribution
*/
enum FadeDirection {
UP,
DOWN
}

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.action;
import org.openhab.binding.dmx.internal.multiverse.DmxChannel;
/**
* Resume action. Restores previously suspended value or actions on an item.
*
* @author Davy Vanherbergen - Initial contribution
* @author Jan N. Klug - Refactoring for ESH
*/
public class ResumeAction extends BaseAction {
@Override
public int getNewValue(DmxChannel channel, long currentTime) {
state = ActionState.COMPLETED;
channel.resumeAction();
return channel.getNewHiResValue(currentTime);
}
}

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link ArtnetBridgeHandlerConfiguration} is a helper class for the base thing handler configuration
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class ArtnetBridgeHandlerConfiguration {
public int universe;
public String address = "";
public String localaddress = "";
public String refreshmode = "standard";
}

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link ChaserThingHandlerConfiguration} is a helper class for the base thing handler configuration
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class ChaserThingHandlerConfiguration {
public String dmxid = "";
public String steps = "";
public boolean resumeafter = false;
}

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link ColorThingHandlerConfiguration} is a helper class for the base thing handler configuration
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class ColorThingHandlerConfiguration {
public String dmxid = "";
public int fadetime = 0;
public int dimtime = 0;
public String turnonvalue = "255,255,255";
public String turnoffvalue = "0,0,0";
public boolean dynamicturnonvalue = false;
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link DimmerThingHandlerConfiguration} is a helper class for the base thing handler configuration
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class DimmerThingHandlerConfiguration {
public String dmxid = "";
public int fadetime = 0;
public int dimtime = 0;
public String turnonvalue = "255";
public String turnoffvalue = "0";
public boolean dynamicturnonvalue = false;
}

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.dmx.internal.DmxBridgeHandler;
/**
* The {@link DmxBridgeHandlerConfiguration} is a helper class for the base thing handler configuration
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class DmxBridgeHandlerConfiguration {
public String applycurve = "";
public int refreshrate = DmxBridgeHandler.DEFAULT_REFRESH_RATE;
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link Lib485BridgeHandlerConfiguration} is a helper class for the base thing handler configuration
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class Lib485BridgeHandlerConfiguration {
public String address = "";
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link SacnBridgeHandlerConfiguration} is a helper class for the base thing handler configuration
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class SacnBridgeHandlerConfiguration {
public int universe;
public String mode = "multicast";
public String address = "";
public String localaddress = "";
public String refreshmode = "standard";
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link TunableWhiteThingHandlerConfiguration} is a helper class for the base thing handler configuration
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class TunableWhiteThingHandlerConfiguration {
public String dmxid = "";
public int fadetime = 0;
public int dimtime = 0;
public String turnonvalue = "255,255";
public String turnoffvalue = "0,0";
public boolean dynamicturnonvalue = false;
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.dmxoverethernet;
/**
* The {@link ArtnetNode} represents a sending or receiving node with address and port
* default address is set to 6454 for ArtNet
*
* @author Jan N. Klug - Initial contribution
*
*/
public class ArtnetNode extends IpNode {
public static final int DEFAULT_PORT = 6454;
/**
* constructor with address
*
* @param addrString address in format address[:port]
*/
public ArtnetNode(String addrString) {
super(addrString);
if (port == 0) {
port = DEFAULT_PORT;
}
}
}

View File

@@ -0,0 +1,110 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.dmxoverethernet;
import org.openhab.binding.dmx.internal.multiverse.Universe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ArtnetPacket} is a ArtNet packet template
*
* @author Jan N. Klug - Initial contribution
*/
public class ArtnetPacket extends DmxOverEthernetPacket {
public static final int ARTNET_MAX_PACKET_LEN = 530;
public static final int ARTNET_MAX_PAYLOAD_SIZE = 512;
private final Logger logger = LoggerFactory.getLogger(ArtnetPacket.class);
/**
* default constructor, creates a packet
*
*/
public ArtnetPacket() {
payloadSize = ARTNET_MAX_PAYLOAD_SIZE;
rawPacket = new byte[ARTNET_MAX_PACKET_LEN];
/* init Artnet header, total length 38 bytes */
rawPacket[0] = 0x41; // packet identifier, 8 bytes
rawPacket[1] = 0x72;
rawPacket[2] = 0x74;
rawPacket[3] = 0x2d;
rawPacket[4] = 0x4e;
rawPacket[5] = 0x65;
rawPacket[6] = 0x74;
rawPacket[7] = 0x00;
rawPacket[8] = 0x00; // OpCode, 2 bytes
rawPacket[9] = 0x50;
rawPacket[10] = 0x00; // protocol version, 2 bytes
rawPacket[11] = 0x0e;
rawPacket[12] = 0x00; // sequence number, 1 byte
rawPacket[13] = 0x00; // physical input
rawPacket[14] = 0x00; // universe, 15 bit
rawPacket[15] = 0x00;
rawPacket[16] = 0x00; // payload size, 2 bytes
rawPacket[17] = 0x01;
}
@Override
public void setPayloadSize(int payloadSize) {
if (payloadSize < Universe.MIN_UNIVERSE_SIZE) {
payloadSize = Universe.MIN_UNIVERSE_SIZE;
logger.error("payload minimum is {} slots", Universe.MIN_UNIVERSE_SIZE);
} else if (payloadSize > Universe.MAX_UNIVERSE_SIZE) {
payloadSize = Universe.MAX_UNIVERSE_SIZE;
logger.warn("coercing payload size to allowed maximum of {} slots", Universe.MAX_UNIVERSE_SIZE);
}
rawPacket[16] = (byte) (payloadSize / 256);
rawPacket[17] = (byte) (payloadSize % 256);
this.payloadSize = payloadSize;
}
@Override
public void setUniverse(int universeId) {
/* observe limits from standard (coerce to range) */
this.universeId = universeId;
/* set universe in packet to universe-1 */
rawPacket[14] = (byte) (this.universeId % 256);
rawPacket[15] = (byte) (this.universeId / 256);
logger.trace("set packet universe to {}", this.universeId);
}
@Override
public void setSequence(int sequenceNo) {
rawPacket[12] = (byte) (sequenceNo % 256);
}
@Override
public void setPayload(byte[] payload) {
System.arraycopy(payload, 0, rawPacket, 18, payloadSize);
}
@Override
public void setPayload(byte[] payload, int payloadSize) {
if (payloadSize != this.payloadSize) {
setPayloadSize(payloadSize);
}
setPayload(payload);
}
@Override
public int getPacketLength() {
return (18 + this.payloadSize);
}
}

View File

@@ -0,0 +1,132 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.dmxoverethernet;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
import org.openhab.binding.dmx.internal.DmxBridgeHandler;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link DmxOverEthernetHandler} is an abstract class with base functions
* for DMX over Ethernet Bridges (ArtNet, sACN)
*
* @author Jan N. Klug - Initial contribution
*/
public abstract class DmxOverEthernetHandler extends DmxBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(DmxOverEthernetHandler.class);
protected DmxOverEthernetPacket packetTemplate;
protected IpNode senderNode = new IpNode();
protected List<IpNode> receiverNodes = new ArrayList<>();
protected boolean refreshAlways = false;
DatagramSocket socket = null;
private long lastSend = 0;
private int repeatCounter = 0;
private int sequenceNo = 0;
@Override
protected void openConnection() {
if (getThing().getStatus() != ThingStatus.ONLINE) {
try {
if (senderNode.getAddress() == null) {
if (senderNode.getPort() == 0) {
socket = new DatagramSocket();
senderNode.setInetAddress(socket.getLocalAddress());
senderNode.setPort(socket.getLocalPort());
} else {
socket = new DatagramSocket(senderNode.getPort());
senderNode.setInetAddress(socket.getLocalAddress());
}
} else {
socket = new DatagramSocket(senderNode.getPort(), senderNode.getAddress());
}
updateStatus(ThingStatus.ONLINE);
logger.debug("opened socket {} in bridge {}", senderNode, this.thing.getUID());
} catch (SocketException e) {
logger.debug("could not open socket {} in bridge {}: {}", senderNode, this.thing.getUID(),
e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "opening UDP socket failed");
}
}
}
@Override
protected void closeConnection() {
if (socket != null) {
logger.debug("closing socket {} in bridge {}", senderNode, this.thing.getUID());
socket.close();
socket = null;
} else {
logger.debug("socket was already closed when calling closeConnection in bridge {}", this.thing.getUID());
}
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "UDP socket closed");
}
@Override
protected void sendDmxData() {
if (getThing().getStatus() == ThingStatus.ONLINE) {
boolean needsSending = false;
long now = System.currentTimeMillis();
universe.calculateBuffer(now);
if ((universe.getLastBufferChanged() > lastSend) || refreshAlways) {
needsSending = true;
repeatCounter = 0;
} else if (now - lastSend > 800) {
needsSending = true;
} else if (repeatCounter < 3) {
needsSending = true;
repeatCounter++;
}
if (needsSending) {
packetTemplate.setPayload(universe.getBuffer(), universe.getBufferSize());
packetTemplate.setSequence(sequenceNo);
DatagramPacket sendPacket = new DatagramPacket(packetTemplate.getRawPacket(),
packetTemplate.getPacketLength());
for (IpNode receiverNode : receiverNodes) {
sendPacket.setAddress(receiverNode.getAddress());
sendPacket.setPort(receiverNode.getPort());
logger.trace("sending packet with length {} to {}", packetTemplate.getPacketLength(),
receiverNode.toString());
try {
socket.send(sendPacket);
} catch (IOException e) {
logger.debug("Could not send to {} in {}: {}", receiverNode, this.thing.getUID(),
e.getMessage());
closeConnection(ThingStatusDetail.COMMUNICATION_ERROR, "could not send DMX data");
}
}
lastSend = now;
sequenceNo = (sequenceNo + 1) % 256;
}
} else {
openConnection();
}
}
public DmxOverEthernetHandler(Bridge sacnBridge) {
super(sacnBridge);
}
}

View File

@@ -0,0 +1,97 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.dmxoverethernet;
/**
* The {@link DmxOverEthernetPacket} is an abstract class for
* DMX over Ethernet packets (ArtNet, sACN)
*
* @author Jan N. Klug - Initial contribution
*/
public abstract class DmxOverEthernetPacket {
protected int universeId;
protected int payloadSize;
protected byte[] rawPacket;
/**
* set payload size
*
* @param payloadSize payload size (number of DMX channels in this packet)
*/
public abstract void setPayloadSize(int payloadSize);
/**
* sets universe, calculates sender name and broadcast-address
*
* @param universe
*/
public abstract void setUniverse(int universeId);
/**
* set sequence number
*
* @param sequenceNo sequence number (0-255)
*/
public abstract void setSequence(int sequenceNo);
/**
* set DMX payload data
*
* @param payload byte array containing DMX channel data
*/
public abstract void setPayload(byte[] payload);
/**
* set payload data
*
* @param payload byte array containing DMX channel data
* @param payloadSize length of data (no. of channels)
*/
public abstract void setPayload(byte[] payload, int payloadSize);
/**
* get packet for transmission
*
* @return byte array with raw packet data
*/
public byte[] getRawPacket() {
return rawPacket;
}
/**
* get packet length
*
* @return full packet length
*/
public abstract int getPacketLength();
/**
* get universe of this packet
*
* @return universe number
*
*/
public int getUniverse() {
return this.universeId;
}
/**
* get payload size
*
* @return number of DMX channels in this packet
*/
public int getPayloadSize() {
return this.payloadSize;
}
}

View File

@@ -0,0 +1,163 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.dmxoverethernet;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link IpNode} represents a sending or receiving node with address and port
*
* @author Jan N. Klug - Initial contribution
*
*/
public class IpNode {
protected static final Pattern ADDR_PATTERN = Pattern.compile("([\\w.-]+):?(\\d*)");
private final Logger logger = LoggerFactory.getLogger(IpNode.class);
protected int port = 0;
protected InetAddress address = null;
/**
* default constructor
*/
public IpNode() {
}
/**
* constructor with address
*
* @param addrString address in format address[:port]
*/
public IpNode(String addrString) {
Matcher addrMatcher = ADDR_PATTERN.matcher(addrString);
if (addrMatcher.matches()) {
setInetAddress(addrMatcher.group(1));
if (!addrMatcher.group(2).isEmpty()) {
setPort(Integer.valueOf(addrMatcher.group(2)));
}
} else {
logger.warn("invalid format {}, returning empty UdpNode", addrString);
}
}
/**
* constructor with address and port
*
* @param addrString domain name or IP address as string representation
* @param port UDP port of the receiver node
*/
public IpNode(String addrString, int port) {
setPort(port);
setInetAddress(addrString);
}
/**
* sets the node address
*
* @param addrString domain name or IP address as string representation
*/
public void setInetAddress(String addrString) {
try {
this.address = InetAddress.getByName(addrString);
} catch (UnknownHostException e) {
this.address = null;
logger.warn("could not set address from {}", addrString);
}
}
/**
* set the node address
*
* @param address inet address
*/
public void setInetAddress(InetAddress address) {
this.address = address;
}
/**
* sets the node port
*
* @param port UDP port of the receiver node
*/
public void setPort(int port) {
this.port = port;
}
/**
* get this nodes port
*
* @return UDP port
*/
public int getPort() {
return this.port;
}
/**
* get this nodes address
*
* @return address as InetAddress
*/
public InetAddress getAddress() {
return address;
}
public String getAddressString() {
String addrString = address.getHostAddress();
return addrString;
}
/**
* convert node to String
*
* @return string representation of this node (address:port)
*/
@Override
public String toString() {
if (this.address == null) {
return "(null):" + String.valueOf(this.port);
}
return this.address.toString() + ":" + String.valueOf(this.port);
}
/**
* create list of nodes from string
*
* @param addrString input string, format: address1[:port],address2
* @param defaultPort default port if none is given in the string
* @return a List of IpNodes
*/
public static List<IpNode> fromString(String addrString, int defaultPort) throws IllegalArgumentException {
List<IpNode> ipNodes = new ArrayList<>();
int port;
for (String singleAddrString : addrString.split(",")) {
Matcher addrMatch = ADDR_PATTERN.matcher(singleAddrString);
if (addrMatch.matches()) {
port = (addrMatch.group(2).isEmpty()) ? defaultPort : Integer.valueOf(addrMatch.group(2));
ipNodes.add(new IpNode(addrMatch.group(1), port));
} else {
throw new IllegalArgumentException(String.format("Node definition {} is not valid.", singleAddrString));
}
}
return ipNodes;
}
}

View File

@@ -0,0 +1,47 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.dmxoverethernet;
/**
* The {@link ArtnetNode} represents a sending or receiving node with address and port
* default address is set to 5568 for sACN/E1.31
*
* @author Jan N. Klug - Initial contribution
*
*/
public class SacnNode extends IpNode {
public static final int DEFAULT_PORT = 5568;
/**
* constructor with address
*
* @param addrString address in format address[:port]
*/
public SacnNode(String addrString) {
super(addrString);
if (port == 0) {
port = DEFAULT_PORT;
}
}
/**
* create a SacnNode with a broadcast-address
*
* @param universeId the universe to create the node for
* @return the multicast SacnNode
*/
public static SacnNode getBroadcastNode(int universeId) {
return new SacnNode(String.format("239.255.%d.%d", universeId / 256, universeId % 256));
}
}

View File

@@ -0,0 +1,184 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.dmxoverethernet;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import org.openhab.binding.dmx.internal.Util;
import org.openhab.binding.dmx.internal.multiverse.Universe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SacnPacket} is responsible for handling commands, which are
* sent to the bridge.
*
* @author Jan N. Klug - Initial contribution
*/
public class SacnPacket extends DmxOverEthernetPacket {
public static final int SACN_MAX_PACKET_LEN = 638;
public static final int SACN_MAX_PAYLOAD_SIZE = 512;
private final Logger logger = LoggerFactory.getLogger(SacnPacket.class);
/**
* default constructor, creates a packet
*
* @param uuid UUID is mandatory
*/
public SacnPacket(UUID uuid) {
payloadSize = SACN_MAX_PAYLOAD_SIZE;
rawPacket = new byte[SACN_MAX_PACKET_LEN];
/* init E1.31 root layer, total length 38 bytes */
rawPacket[0] = 0x00; // preamble size, 2 bytes
rawPacket[1] = 0x10;
rawPacket[2] = 0x00; // postamble size, 2 bytes
rawPacket[3] = 0x00;
rawPacket[4] = 0x41; // packet identifier, 12 bytes
rawPacket[5] = 0x53;
rawPacket[6] = 0x43;
rawPacket[7] = 0x2d;
rawPacket[8] = 0x45;
rawPacket[9] = 0x31;
rawPacket[10] = 0x2e;
rawPacket[11] = 0x31;
rawPacket[12] = 0x37;
rawPacket[13] = 0x00;
rawPacket[14] = 0x00;
rawPacket[15] = 0x00;
rawPacket[16] = 0x72; // flags & length, 2 bytes
rawPacket[17] = 0x6e;
rawPacket[18] = 0x00; // vector, 4 bytes;
rawPacket[19] = 0x00;
rawPacket[20] = 0x00;
rawPacket[21] = 0x04;
// UUID 16 bytes
ByteBuffer uuidBytes = ByteBuffer.wrap(new byte[16]);
uuidBytes.putLong(uuid.getMostSignificantBits());
uuidBytes.putLong(uuid.getLeastSignificantBits());
System.arraycopy(uuidBytes.array(), 0, rawPacket, 22, 16);
/* init sACN/E1.31 framing layer, total length 77 bytes */
rawPacket[38] = 0x72; // flags & length, 2 bytes
rawPacket[39] = 0x58;
rawPacket[40] = 0x00; // vector, 4 bytes;
rawPacket[41] = 0x00;
rawPacket[42] = 0x00;
rawPacket[43] = 0x02;
for (int i = 44; i < 108; i++) { // senderName, 64 bytes
rawPacket[i] = 0x00;
}
rawPacket[108] = 0x64; // priority (default 100), 1 byte
rawPacket[109] = 0x00; // reserved, 2 bytes
rawPacket[110] = 0x00;
rawPacket[111] = 0x00; // sequence number, 1 byte
rawPacket[112] = 0x00; // options, 1 byte
rawPacket[113] = 0x00; // universe, 2 bytes
rawPacket[114] = 0x00;
/* sACN/E1.31 DMP layer, total length 11 + channel count */
rawPacket[115] = 0x72; // flags & length, 2 bytes
rawPacket[116] = 0x0b;
rawPacket[117] = 0x02; // vector, 1 byte
rawPacket[118] = (byte) 0xa1; // address type, 1 byte
rawPacket[119] = 0x00; // start address, 2 bytes
rawPacket[120] = 0x00;
rawPacket[121] = 0x00; // address increment, 2 bytes
rawPacket[122] = 0x01;
rawPacket[123] = 0x02; // payload size, 2 bytes (including start code)
rawPacket[123] = 0x01;
rawPacket[125] = 0x00; // DMX start code, 1 byte
}
@Override
public void setPayloadSize(int payloadSize) throws IllegalArgumentException {
if (payloadSize < Universe.MIN_UNIVERSE_SIZE) {
throw new IllegalArgumentException(
String.format("payload minimum size is %d slots (>%d)", Universe.MIN_UNIVERSE_SIZE, payloadSize));
} else if (payloadSize > Universe.MAX_UNIVERSE_SIZE) {
throw new IllegalArgumentException(
String.format("payload maximum size is %d slots (<%d)", Universe.MAX_UNIVERSE_SIZE, payloadSize));
}
/* root Layer */
rawPacket[16] = (byte) ((28672 + 110 + payloadSize) / 256);
rawPacket[17] = (byte) ((28672 + 110 + payloadSize) % 256);
/* framing layer */
rawPacket[38] = (byte) ((28672 + 88 + payloadSize) / 256);
rawPacket[39] = (byte) ((28672 + 88 + payloadSize) % 256);
/* DMP layer */
rawPacket[115] = (byte) ((28672 + 11 + payloadSize) / 256);
rawPacket[116] = (byte) ((28672 + 11 + payloadSize) % 256);
rawPacket[123] = (byte) ((payloadSize + 1) / 256);
rawPacket[124] = (byte) ((payloadSize + 1) % 256);
this.payloadSize = payloadSize;
}
@Override
public void setUniverse(int universeId) {
this.universeId = universeId;
/* set universe in packet */
rawPacket[113] = (byte) (this.universeId / 256);
rawPacket[114] = (byte) (this.universeId % 256);
/* set sender name in packet */
String senderName = new String("ESH DMX binding (sACN) <" + String.format("%05d", this.universeId) + ">");
byte[] senderNameBytes = senderName.getBytes(StandardCharsets.UTF_8);
System.arraycopy(senderNameBytes, 0, rawPacket, 44, senderName.length());
logger.trace("set packet universe to {}", this.universeId);
}
@Override
public void setSequence(int sequenceNo) {
rawPacket[111] = (byte) (sequenceNo % 256);
}
/**
* set priority
*
* @param priority data priority (for multiple senders), allowed values are 0-200, default 100
*/
public void setPriority(int priority) {
/* observe limits (coerce to range) */
rawPacket[108] = (byte) Util.coerceToRange(priority, 0, 200, logger, "packet priority");
logger.debug("set packet priority to {}", priority);
}
@Override
public void setPayload(byte[] payload) {
System.arraycopy(payload, 0, rawPacket, 126, payloadSize);
}
@Override
public void setPayload(byte[] payload, int payloadSize) {
if (payloadSize != this.payloadSize) {
setPayloadSize(payloadSize);
}
setPayload(payload);
}
@Override
public int getPacketLength() {
return (126 + this.payloadSize);
}
}

View File

@@ -0,0 +1,95 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.handler;
import static org.openhab.binding.dmx.internal.DmxBindingConstants.THING_TYPE_ARTNET_BRIDGE;
import java.util.Collections;
import java.util.Set;
import org.openhab.binding.dmx.internal.config.ArtnetBridgeHandlerConfiguration;
import org.openhab.binding.dmx.internal.dmxoverethernet.ArtnetNode;
import org.openhab.binding.dmx.internal.dmxoverethernet.ArtnetPacket;
import org.openhab.binding.dmx.internal.dmxoverethernet.DmxOverEthernetHandler;
import org.openhab.binding.dmx.internal.dmxoverethernet.IpNode;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ArtnetBridgeHandler} is responsible for handling the communication
* with ArtNet devices
*
* @author Jan N. Klug - Initial contribution
*/
public class ArtnetBridgeHandler extends DmxOverEthernetHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_ARTNET_BRIDGE);
public static final int MIN_UNIVERSE_ID = 0;
public static final int MAX_UNIVERSE_ID = 32767;
private final Logger logger = LoggerFactory.getLogger(ArtnetBridgeHandler.class);
public ArtnetBridgeHandler(Bridge artnetBridge) {
super(artnetBridge);
}
@Override
protected void updateConfiguration() {
ArtnetBridgeHandlerConfiguration configuration = getConfig().as(ArtnetBridgeHandlerConfiguration.class);
setUniverse(configuration.universe, MIN_UNIVERSE_ID, MAX_UNIVERSE_ID);
packetTemplate.setUniverse(universe.getUniverseId());
receiverNodes.clear();
if (configuration.address.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Could not initialize sender (address not set)");
uninstallScheduler();
logger.debug("remote address not set for {}", this.thing.getUID());
return;
} else {
try {
receiverNodes = IpNode.fromString(configuration.address, ArtnetNode.DEFAULT_PORT);
logger.debug("using unicast mode to {} for {}", receiverNodes.toString(), this.thing.getUID());
} catch (IllegalArgumentException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
return;
}
}
if (!configuration.localaddress.isEmpty()) {
senderNode = new IpNode(configuration.localaddress);
}
logger.debug("originating address is {} for {}", senderNode, this.thing.getUID());
refreshAlways = configuration.refreshmode.equals("always");
logger.debug("refresh mode set to always: {}", refreshAlways);
updateStatus(ThingStatus.UNKNOWN);
super.updateConfiguration();
logger.debug("updated configuration for ArtNet bridge {}", this.thing.getUID());
}
@Override
public void initialize() {
logger.debug("initializing ArtNet bridge {}", this.thing.getUID());
packetTemplate = new ArtnetPacket();
updateConfiguration();
}
}

View File

@@ -0,0 +1,216 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.handler;
import static org.openhab.binding.dmx.internal.DmxBindingConstants.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.openhab.binding.dmx.internal.DmxBindingConstants.ListenerType;
import org.openhab.binding.dmx.internal.DmxBridgeHandler;
import org.openhab.binding.dmx.internal.DmxThingHandler;
import org.openhab.binding.dmx.internal.ValueSet;
import org.openhab.binding.dmx.internal.action.FadeAction;
import org.openhab.binding.dmx.internal.action.ResumeAction;
import org.openhab.binding.dmx.internal.config.ChaserThingHandlerConfiguration;
import org.openhab.binding.dmx.internal.multiverse.BaseDmxChannel;
import org.openhab.binding.dmx.internal.multiverse.DmxChannel;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ChaserThingHandler} is responsible for handling commands, which are
* sent to the chaser.
*
* @author Jan N. Klug - Initial contribution
*/
public class ChaserThingHandler extends DmxThingHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_CHASER);
private final Logger logger = LoggerFactory.getLogger(ChaserThingHandler.class);
private final List<DmxChannel> channels = new ArrayList<>();
private List<ValueSet> values = new ArrayList<>();
private boolean resumeAfter = false;
private OnOffType isRunning = OnOffType.OFF;
public ChaserThingHandler(Thing dimmerThing) {
super(dimmerThing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
switch (channelUID.getId()) {
case CHANNEL_SWITCH:
if (command instanceof OnOffType) {
if (((OnOffType) command).equals(OnOffType.ON)) {
Integer channelCounter = 0;
for (DmxChannel channel : channels) {
if (resumeAfter) {
channel.suspendAction();
} else {
channel.clearAction();
}
for (ValueSet value : values) {
channel.addChannelAction(new FadeAction(value.getFadeTime(),
value.getValue(channelCounter), value.getHoldTime()));
}
if (resumeAfter) {
channel.addChannelAction(new ResumeAction());
}
channel.addListener(channelUID, this, ListenerType.ACTION);
channelCounter++;
}
} else {
for (DmxChannel channel : channels) {
if (resumeAfter && channel.isSuspended()) {
channel.setChannelAction(new ResumeAction());
} else {
channel.clearAction();
}
}
}
} else if (command instanceof RefreshType) {
updateState(channelUID, isRunning);
} else {
logger.debug("command {} not supported in channel {}:switch", command.getClass(),
this.thing.getUID());
}
break;
case CHANNEL_CONTROL:
if (command instanceof StringType) {
List<ValueSet> newValues = ValueSet.parseChaseConfig(((StringType) command).toString());
if (!newValues.isEmpty()) {
values = newValues;
logger.debug("updated chase config in {}", this.thing.getUID());
} else {
logger.debug("could not update chase config in {}, malformed: {}", this.thing.getUID(),
command);
}
} else {
logger.debug("command {} not supported in channel {}:control", command.getClass(),
this.thing.getUID());
}
break;
default:
logger.debug("Channel {} not supported in thing {}", channelUID.getId(), this.thing.getUID());
}
}
@Override
public void initialize() {
Bridge bridge = getBridge();
DmxBridgeHandler bridgeHandler;
if (bridge == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "no bridge assigned");
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
return;
} else {
bridgeHandler = (DmxBridgeHandler) bridge.getHandler();
if (bridgeHandler == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "no bridge handler available");
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
return;
}
}
ChaserThingHandlerConfiguration configuration = getConfig().as(ChaserThingHandlerConfiguration.class);
if (configuration.dmxid.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"DMX channel configuration missing");
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
return;
}
try {
List<BaseDmxChannel> configChannels = BaseDmxChannel.fromString(configuration.dmxid,
bridgeHandler.getUniverseId());
logger.trace("found {} channels in {}", configChannels.size(), this.thing.getUID());
for (BaseDmxChannel channel : configChannels) {
channels.add(bridgeHandler.getDmxChannel(channel, this.thing));
}
} catch (IllegalArgumentException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
return;
}
if (!configuration.steps.isEmpty()) {
values = ValueSet.parseChaseConfig(configuration.steps);
if (!values.isEmpty()) {
if (bridge.getStatus().equals(ThingStatus.ONLINE)) {
updateStatus(ThingStatus.ONLINE);
dmxHandlerStatus = ThingStatusDetail.NONE;
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"chaser configuration malformed");
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Chase configuration missing");
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
}
resumeAfter = configuration.resumeafter;
logger.trace("set resumeAfter to {}", resumeAfter);
}
@Override
public void dispose() {
if (!channels.isEmpty()) {
Bridge bridge = getBridge();
if (bridge != null) {
DmxBridgeHandler bridgeHandler = (DmxBridgeHandler) bridge.getHandler();
if (bridgeHandler != null) {
bridgeHandler.unregisterDmxChannels(this.thing);
logger.debug("removing {} channels from {}", channels.size(), this.thing.getUID());
}
ChannelUID switchChannelUID = new ChannelUID(this.thing.getUID(), CHANNEL_SWITCH);
for (DmxChannel channel : channels) {
channel.removeListener(switchChannelUID);
}
}
channels.clear();
}
}
@Override
public void updateSwitchState(ChannelUID channelUID, State state) {
logger.trace("received {} for {}", state, channelUID);
if (channelUID.getId().equals(CHANNEL_SWITCH) && (state instanceof OnOffType)) {
this.isRunning = (OnOffType) state;
updateState(channelUID, state);
} else {
logger.debug("unknown state received: {} in channel {} thing {}", state, channelUID, this.thing.getUID());
}
}
}

View File

@@ -0,0 +1,322 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.handler;
import static org.openhab.binding.dmx.internal.DmxBindingConstants.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.IntStream;
import org.openhab.binding.dmx.internal.DmxBindingConstants.ListenerType;
import org.openhab.binding.dmx.internal.DmxBridgeHandler;
import org.openhab.binding.dmx.internal.DmxThingHandler;
import org.openhab.binding.dmx.internal.Util;
import org.openhab.binding.dmx.internal.ValueSet;
import org.openhab.binding.dmx.internal.action.FadeAction;
import org.openhab.binding.dmx.internal.config.ColorThingHandlerConfiguration;
import org.openhab.binding.dmx.internal.multiverse.BaseDmxChannel;
import org.openhab.binding.dmx.internal.multiverse.DmxChannel;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ColorThingHandler} is responsible for handling commands, which are
* sent to the dimmer.
*
* @author Jan N. Klug - Initial contribution
*/
public class ColorThingHandler extends DmxThingHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_COLOR);
private final Logger logger = LoggerFactory.getLogger(ColorThingHandler.class);
private final List<DmxChannel> channels = new ArrayList<>();
private final List<Integer> currentValues = new ArrayList<>();
private HSBType currentColor = new HSBType();
private ValueSet turnOnValue = new ValueSet(0, -1, DmxChannel.MAX_VALUE);
private ValueSet turnOffValue = new ValueSet(0, -1, DmxChannel.MIN_VALUE);
private int fadeTime = 0;
private int dimTime = 0;
private boolean dynamicTurnOnValue = false;
private boolean isDimming = false;
public ColorThingHandler(Thing dimmerThing) {
super(dimmerThing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.trace("received command {} in channel {}", command, channelUID);
ValueSet targetValueSet = new ValueSet(fadeTime, -1);
switch (channelUID.getId()) {
case CHANNEL_BRIGHTNESS_R:
if (command instanceof RefreshType) {
logger.trace("sending update on refresh to channel {}:brightness_r", this.thing.getUID());
currentValues.set(0, channels.get(0).getValue());
updateCurrentColor();
updateState(channelUID, Util.toPercentValue(currentValues.get(0)));
return;
} else {
logger.debug("command {} not supported in channel {}:brightness_r", command.getClass(),
this.thing.getUID());
return;
}
case CHANNEL_BRIGHTNESS_G:
if (command instanceof RefreshType) {
logger.trace("sending update on refresh to channel {}:brightness_g", this.thing.getUID());
currentValues.set(1, channels.get(1).getValue());
updateCurrentColor();
updateState(channelUID, Util.toPercentValue(currentValues.get(1)));
return;
} else {
logger.debug("command {} not supported in channel {}:brightness_g", command.getClass(),
this.thing.getUID());
return;
}
case CHANNEL_BRIGHTNESS_B:
if (command instanceof RefreshType) {
logger.trace("sending update on refresh to channel {}:brightness_b", this.thing.getUID());
currentValues.set(2, channels.get(2).getValue());
updateCurrentColor();
updateState(channelUID, Util.toPercentValue(currentValues.get(2)));
return;
} else {
logger.debug("command {} not supported in channel {}:brightness_b", command.getClass(),
this.thing.getUID());
return;
}
case CHANNEL_COLOR: {
if (command instanceof OnOffType) {
logger.trace("adding {} fade to channels in thing {}", command, this.thing.getUID());
if (((OnOffType) command) == OnOffType.ON) {
targetValueSet = turnOnValue;
} else {
if (dynamicTurnOnValue) {
turnOnValue.clear();
for (DmxChannel channel : channels) {
turnOnValue.addValue(channel.getValue());
}
logger.trace("stored channel values fort next turn-on");
}
targetValueSet = turnOffValue;
}
} else if (command instanceof HSBType) {
logger.trace("adding color fade to channels in thing {}", this.thing.getUID());
targetValueSet.addValue(((HSBType) command).getRed());
targetValueSet.addValue(((HSBType) command).getGreen());
targetValueSet.addValue(((HSBType) command).getBlue());
} else if ((command instanceof PercentType) || (command instanceof DecimalType)) {
logger.trace("adding brightness fade to channels in thing {}", this.thing.getUID());
PercentType brightness = (command instanceof PercentType) ? (PercentType) command
: Util.toPercentValue(((DecimalType) command).intValue());
HSBType targetColor = new HSBType(currentColor.getHue(), currentColor.getSaturation(), brightness);
targetValueSet.addValue(targetColor.getRed());
targetValueSet.addValue(targetColor.getGreen());
targetValueSet.addValue(targetColor.getBlue());
} else if (command instanceof IncreaseDecreaseType) {
if (isDimming && ((IncreaseDecreaseType) command).equals(IncreaseDecreaseType.INCREASE)) {
logger.trace("stopping fade in thing {}", this.thing.getUID());
channels.forEach(DmxChannel::clearAction);
isDimming = false;
return;
} else {
logger.trace("starting {} fade in thing {}", command, this.thing.getUID());
HSBType targetColor;
if (((IncreaseDecreaseType) command).equals(IncreaseDecreaseType.INCREASE)) {
targetColor = new HSBType(currentColor.getHue(), currentColor.getSaturation(),
PercentType.HUNDRED);
} else {
targetColor = new HSBType(currentColor.getHue(), currentColor.getSaturation(),
PercentType.ZERO);
}
targetValueSet.addValue(targetColor.getRed());
targetValueSet.addValue(targetColor.getGreen());
targetValueSet.addValue(targetColor.getBlue());
targetValueSet.setFadeTime(dimTime);
isDimming = true;
}
} else if (command instanceof RefreshType) {
logger.trace("sending update on refresh to channel {}:color", this.thing.getUID());
currentValues.set(0, channels.get(0).getValue());
currentValues.set(1, channels.get(1).getValue());
currentValues.set(2, channels.get(2).getValue());
updateCurrentColor();
updateState(channelUID, currentColor);
return;
} else {
logger.debug("command {} not supported in channel {}:color", command.getClass(),
this.thing.getUID());
return;
}
break;
}
default:
logger.debug("channel {} not supported in thing {}", channelUID.getId(), this.thing.getUID());
return;
}
final ValueSet valueSet = targetValueSet;
IntStream.range(0, channels.size()).forEach(i -> {
channels.get(i).setChannelAction(new FadeAction(valueSet.getFadeTime(), channels.get(i).getValue(),
valueSet.getValue(i), valueSet.getHoldTime()));
});
}
@Override
public void initialize() {
Bridge bridge = getBridge();
DmxBridgeHandler bridgeHandler;
if (bridge == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "no bridge assigned");
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
return;
} else {
bridgeHandler = (DmxBridgeHandler) bridge.getHandler();
if (bridgeHandler == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "no bridge handler available");
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
return;
}
}
ColorThingHandlerConfiguration configuration = getConfig().as(ColorThingHandlerConfiguration.class);
if (configuration.dmxid.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"DMX channel configuration missing");
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
return;
}
try {
List<BaseDmxChannel> configChannels = BaseDmxChannel.fromString(configuration.dmxid,
bridgeHandler.getUniverseId());
logger.trace("found {} channels in {}", configChannels.size(), this.thing.getUID());
for (BaseDmxChannel channel : configChannels) {
channels.add(bridgeHandler.getDmxChannel(channel, this.thing));
}
} catch (IllegalArgumentException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
return;
}
currentValues.add(DmxChannel.MIN_VALUE);
currentValues.add(DmxChannel.MIN_VALUE);
currentValues.add(DmxChannel.MIN_VALUE);
fadeTime = configuration.fadetime;
logger.trace("setting fadeTime to {} ms in {}", fadeTime, this.thing.getUID());
dimTime = configuration.dimtime;
logger.trace("setting dimTime to {} ms in {}", fadeTime, this.thing.getUID());
String turnOnValueString = String.valueOf(fadeTime) + ":" + configuration.turnonvalue + ":-1";
ValueSet turnOnValue = ValueSet.fromString(turnOnValueString);
if (turnOnValue.size() % 3 == 0) {
this.turnOnValue = turnOnValue;
logger.trace("set turnonvalue to {} in {}", turnOnValue, this.thing.getUID());
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "turn-on value malformed");
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
return;
}
this.turnOnValue.setFadeTime(fadeTime);
dynamicTurnOnValue = configuration.dynamicturnonvalue;
String turnOffValueString = String.valueOf(fadeTime) + ":" + configuration.turnoffvalue + ":-1";
ValueSet turnOffValue = ValueSet.fromString(turnOffValueString);
if (turnOffValue.size() % 3 == 0) {
this.turnOffValue = turnOffValue;
logger.trace("set turnoffvalue to {} in {}", turnOffValue, this.thing.getUID());
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "turn-off value malformed");
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
return;
}
this.turnOffValue.setFadeTime(fadeTime);
// register feedback listeners
channels.get(0).addListener(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS_R), this,
ListenerType.VALUE);
channels.get(1).addListener(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS_G), this,
ListenerType.VALUE);
channels.get(2).addListener(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS_B), this,
ListenerType.VALUE);
if (bridge.getStatus().equals(ThingStatus.ONLINE)) {
updateStatus(ThingStatus.ONLINE);
dmxHandlerStatus = ThingStatusDetail.NONE;
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
}
@Override
public void dispose() {
if (!channels.isEmpty()) {
channels.get(0).removeListener(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS_R));
channels.get(1).removeListener(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS_G));
channels.get(2).removeListener(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS_B));
}
channels.clear();
currentValues.clear();
currentColor = new HSBType();
}
@Override
public void updateChannelValue(ChannelUID channelUID, int value) {
updateState(channelUID, Util.toPercentValue(value));
switch (channelUID.getId()) {
case CHANNEL_BRIGHTNESS_R:
currentValues.set(0, value);
break;
case CHANNEL_BRIGHTNESS_G:
currentValues.set(1, value);
break;
case CHANNEL_BRIGHTNESS_B:
currentValues.set(2, value);
break;
default:
logger.debug("don't know how to handle {} in RGB type", channelUID.getId());
return;
}
updateCurrentColor();
updateState(new ChannelUID(this.thing.getUID(), CHANNEL_COLOR), currentColor);
logger.trace("received update {} in channel {}, result is {}", value, channelUID, currentColor);
}
private void updateCurrentColor() {
currentColor = HSBType.fromRGB(currentValues.get(0), currentValues.get(1), currentValues.get(2));
}
}

View File

@@ -0,0 +1,250 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.handler;
import static org.openhab.binding.dmx.internal.DmxBindingConstants.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.IntStream;
import org.openhab.binding.dmx.internal.DmxBindingConstants.ListenerType;
import org.openhab.binding.dmx.internal.DmxBridgeHandler;
import org.openhab.binding.dmx.internal.DmxThingHandler;
import org.openhab.binding.dmx.internal.Util;
import org.openhab.binding.dmx.internal.ValueSet;
import org.openhab.binding.dmx.internal.action.FadeAction;
import org.openhab.binding.dmx.internal.config.DimmerThingHandlerConfiguration;
import org.openhab.binding.dmx.internal.multiverse.BaseDmxChannel;
import org.openhab.binding.dmx.internal.multiverse.DmxChannel;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link DimmerThingHandler} is responsible for handling commands, which are
* sent to the dimmer.
*
* @author Jan N. Klug - Initial contribution
*/
public class DimmerThingHandler extends DmxThingHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_DIMMER);
private final Logger logger = LoggerFactory.getLogger(DimmerThingHandler.class);
private final List<DmxChannel> channels = new ArrayList<>();
private PercentType currentBrightness = PercentType.ZERO;
private ValueSet turnOnValue = new ValueSet(0, -1, DmxChannel.MAX_VALUE);
private ValueSet turnOffValue = new ValueSet(0, -1, DmxChannel.MIN_VALUE);
private int fadeTime = 0, dimTime = 0;
private boolean dynamicTurnOnValue = false;
private boolean isDimming = false;
public DimmerThingHandler(Thing dimmerThing) {
super(dimmerThing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.trace("received command {} in channel {}", command, channelUID);
ValueSet targetValueSet = new ValueSet(fadeTime, -1);
switch (channelUID.getId()) {
case CHANNEL_BRIGHTNESS: {
if (command instanceof PercentType || command instanceof DecimalType) {
PercentType brightness = (command instanceof PercentType) ? (PercentType) command
: Util.toPercentValue(((DecimalType) command).intValue());
logger.trace("adding fade to channels in thing {}", this.thing.getUID());
targetValueSet.addValue(brightness);
} else if (command instanceof OnOffType) {
logger.trace("adding {} fade to channels in thing {}", command, this.thing.getUID());
if (((OnOffType) command) == OnOffType.ON) {
targetValueSet = turnOnValue;
} else {
if (dynamicTurnOnValue) {
turnOnValue.clear();
for (DmxChannel channel : channels) {
turnOnValue.addValue(channel.getValue());
}
logger.trace("stored channel values fort next turn-on");
}
targetValueSet = turnOffValue;
}
} else if (command instanceof IncreaseDecreaseType) {
if (isDimming && ((IncreaseDecreaseType) command).equals(IncreaseDecreaseType.INCREASE)) {
logger.trace("stopping fade in thing {}", this.thing.getUID());
channels.forEach(DmxChannel::clearAction);
isDimming = false;
return;
} else {
logger.trace("starting {} fade in thing {}", command, this.thing.getUID());
targetValueSet = ((IncreaseDecreaseType) command).equals(IncreaseDecreaseType.INCREASE)
? turnOnValue
: turnOffValue;
targetValueSet.setFadeTime(dimTime);
isDimming = true;
}
} else if (command instanceof RefreshType) {
logger.trace("sending update on refresh to channel {}:brightness", this.thing.getUID());
currentBrightness = Util.toPercentValue(channels.get(0).getValue());
updateState(channelUID, currentBrightness);
return;
} else {
logger.debug("command {} not supported in channel {}:brightness", command.getClass(),
this.thing.getUID());
return;
}
break;
}
default:
logger.debug("channel {} not supported in thing {}", channelUID.getId(), this.thing.getUID());
return;
}
final ValueSet valueSet = targetValueSet;
IntStream.range(0, channels.size()).forEach(i -> {
channels.get(i).setChannelAction(new FadeAction(valueSet.getFadeTime(), channels.get(i).getValue(),
valueSet.getValue(i), valueSet.getHoldTime()));
});
}
@Override
public void initialize() {
Bridge bridge = getBridge();
DmxBridgeHandler bridgeHandler;
if (bridge == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "no bridge assigned");
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
return;
} else {
bridgeHandler = (DmxBridgeHandler) bridge.getHandler();
if (bridgeHandler == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "no bridge handler available");
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
return;
}
}
DimmerThingHandlerConfiguration configuration = getConfig().as(DimmerThingHandlerConfiguration.class);
if (configuration.dmxid.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"DMX channel configuration missing");
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
return;
}
try {
List<BaseDmxChannel> configChannels = BaseDmxChannel.fromString(configuration.dmxid,
bridgeHandler.getUniverseId());
logger.trace("found {} channels in {}", configChannels.size(), this.thing.getUID());
for (BaseDmxChannel channel : configChannels) {
channels.add(bridgeHandler.getDmxChannel(channel, this.thing));
}
} catch (IllegalArgumentException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
return;
}
fadeTime = configuration.fadetime;
logger.trace("setting fadeTime to {} ms in {}", fadeTime, this.thing.getUID());
dimTime = configuration.dimtime;
logger.trace("setting dimTime to {} ms in {}", fadeTime, this.thing.getUID());
String turnOnValueString = String.valueOf(fadeTime) + ":" + configuration.turnonvalue + ":-1";
ValueSet turnOnValue = ValueSet.fromString(turnOnValueString);
if (!turnOnValue.isEmpty()) {
this.turnOnValue = turnOnValue;
logger.trace("set turnonvalue to {} in {}", turnOnValue, this.thing.getUID());
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "turn-on value malformed");
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
return;
}
this.turnOnValue.setFadeTime(fadeTime);
dynamicTurnOnValue = configuration.dynamicturnonvalue;
String turnOffValueString = String.valueOf(fadeTime) + ":" + configuration.turnoffvalue + ":-1";
ValueSet turnOffValue = ValueSet.fromString(turnOffValueString);
if (!turnOffValue.isEmpty()) {
this.turnOffValue = turnOffValue;
logger.trace("set turnoffvalue to {} in {}", turnOffValue, this.thing.getUID());
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "turn-off value malformed");
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
return;
}
this.turnOffValue.setFadeTime(fadeTime);
// register feedback listener
channels.get(0).addListener(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS), this, ListenerType.VALUE);
if (bridge.getStatus().equals(ThingStatus.ONLINE)) {
updateStatus(ThingStatus.ONLINE);
dmxHandlerStatus = ThingStatusDetail.NONE;
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
}
@Override
public void dispose() {
if (!channels.isEmpty()) {
channels.get(0).removeListener(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS));
Bridge bridge = getBridge();
if (bridge != null) {
DmxBridgeHandler bridgeHandler = (DmxBridgeHandler) bridge.getHandler();
if (bridgeHandler != null) {
bridgeHandler.unregisterDmxChannels(this.thing);
logger.debug("removing {} channels from {}", channels.size(), this.thing.getUID());
}
}
channels.clear();
}
}
@Override
public void updateChannelValue(ChannelUID channelUID, int value) {
updateState(channelUID, Util.toPercentValue(value));
if (channelUID.getId().equals(CHANNEL_BRIGHTNESS)) {
currentBrightness = Util.toPercentValue(value);
} else {
logger.debug("don't know how to handle {}", channelUID.getId());
return;
}
logger.trace("received update {} in channel {}, resulting in brightness={}", value, channelUID,
currentBrightness);
}
}

View File

@@ -0,0 +1,160 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.handler;
import static org.openhab.binding.dmx.internal.DmxBindingConstants.THING_TYPE_LIB485_BRIDGE;
import java.io.IOException;
import java.net.Socket;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.openhab.binding.dmx.internal.DmxBridgeHandler;
import org.openhab.binding.dmx.internal.config.Lib485BridgeHandlerConfiguration;
import org.openhab.binding.dmx.internal.dmxoverethernet.IpNode;
import org.openhab.binding.dmx.internal.multiverse.Universe;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link Lib485BridgeHandler} is responsible for communication with
* an Lib485 instance
*
* @author Jan N. Klug - Initial contribution
*/
public class Lib485BridgeHandler extends DmxBridgeHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_LIB485_BRIDGE);
public static final int MIN_UNIVERSE_ID = 0;
public static final int MAX_UNIVERSE_ID = 0;
public static final int DEFAULT_PORT = 9020;
private final Logger logger = LoggerFactory.getLogger(Lib485BridgeHandler.class);
private final Map<IpNode, Socket> receiverNodes = new HashMap<>();
public Lib485BridgeHandler(Bridge lib485Bridge) {
super(lib485Bridge);
}
@Override
protected void openConnection() {
if (getThing().getStatus() != ThingStatus.ONLINE) {
for (IpNode receiverNode : receiverNodes.keySet()) {
Socket socket = receiverNodes.get(receiverNode);
if (socket == null) {
try {
socket = new Socket(receiverNode.getAddressString(), receiverNode.getPort());
} catch (IOException e) {
logger.debug("Could not connect to {} in {}: {}", receiverNode, this.thing.getUID(),
e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"could not connect to " + receiverNode.toString());
return;
}
}
if (socket.isConnected()) {
receiverNodes.put(receiverNode, socket);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
receiverNodes.put(receiverNode, null);
return;
}
}
updateStatus(ThingStatus.ONLINE);
}
}
@Override
protected void closeConnection() {
for (IpNode receiverNode : receiverNodes.keySet()) {
Socket socket = receiverNodes.get(receiverNode);
if ((socket != null) && (!socket.isClosed())) {
try {
socket.close();
} catch (IOException e) {
logger.warn("Could not close socket {} in {}: {}", receiverNode, this.thing.getUID(),
e.getMessage());
}
}
receiverNodes.put(receiverNode, null);
}
}
@Override
protected void sendDmxData() {
if (getThing().getStatus() == ThingStatus.ONLINE) {
long now = System.currentTimeMillis();
universe.calculateBuffer(now);
for (IpNode receiverNode : receiverNodes.keySet()) {
Socket socket = receiverNodes.get(receiverNode);
if (socket.isConnected()) {
try {
socket.getOutputStream().write(universe.getBuffer());
} catch (IOException e) {
logger.debug("Could not send to {} in {}: {}", receiverNode, this.thing.getUID(),
e.getMessage());
closeConnection(ThingStatusDetail.COMMUNICATION_ERROR, "could not send DMX data");
return;
}
} else {
closeConnection(ThingStatusDetail.NONE, "reconnect");
return;
}
}
} else {
openConnection();
}
}
@Override
protected void updateConfiguration() {
Lib485BridgeHandlerConfiguration configuration = getConfig().as(Lib485BridgeHandlerConfiguration.class);
universe = new Universe(MIN_UNIVERSE_ID);
receiverNodes.clear();
if (configuration.address.isEmpty()) {
receiverNodes.put(new IpNode("localhost:9020"), null);
logger.debug("sending to {} for {}", receiverNodes, this.thing.getUID());
} else {
try {
for (IpNode receiverNode : IpNode.fromString(configuration.address, DEFAULT_PORT)) {
receiverNodes.put(receiverNode, null);
logger.debug("sending to {} for {}", receiverNode, this.thing.getUID());
}
} catch (IllegalArgumentException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
return;
}
}
super.updateConfiguration();
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE);
logger.debug("updated configuration for Lib485 bridge {}", this.thing.getUID());
}
@Override
public void initialize() {
logger.debug("initializing Lib485 bridge {}", this.thing.getUID());
updateConfiguration();
}
}

View File

@@ -0,0 +1,102 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.handler;
import static org.openhab.binding.dmx.internal.DmxBindingConstants.THING_TYPE_SACN_BRIDGE;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
import java.util.UUID;
import org.openhab.binding.dmx.internal.config.SacnBridgeHandlerConfiguration;
import org.openhab.binding.dmx.internal.dmxoverethernet.DmxOverEthernetHandler;
import org.openhab.binding.dmx.internal.dmxoverethernet.IpNode;
import org.openhab.binding.dmx.internal.dmxoverethernet.SacnNode;
import org.openhab.binding.dmx.internal.dmxoverethernet.SacnPacket;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SacnBridgeHandler} is responsible for handling the communication
* with sACN/E1.31 devices
*
* @author Jan N. Klug - Initial contribution
*/
public class SacnBridgeHandler extends DmxOverEthernetHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_SACN_BRIDGE);
public static final int MIN_UNIVERSE_ID = 1;
public static final int MAX_UNIVERSE_ID = 63999;
private final Logger logger = LoggerFactory.getLogger(SacnBridgeHandler.class);
private final UUID senderUUID;
public SacnBridgeHandler(Bridge sacnBridge) {
super(sacnBridge);
senderUUID = UUID.randomUUID();
}
@Override
protected void updateConfiguration() {
SacnBridgeHandlerConfiguration configuration = getConfig().as(SacnBridgeHandlerConfiguration.class);
setUniverse(configuration.universe, MIN_UNIVERSE_ID, MAX_UNIVERSE_ID);
packetTemplate.setUniverse(universe.getUniverseId());
receiverNodes.clear();
if ((configuration.mode.equals("unicast"))) {
if (configuration.address.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Could not initialize unicast sender (address not set)");
return;
} else {
try {
receiverNodes = IpNode.fromString(configuration.address, SacnNode.DEFAULT_PORT);
logger.debug("using unicast mode to {} for {}", receiverNodes.toString(), this.thing.getUID());
} catch (IllegalArgumentException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
return;
}
}
} else {
receiverNodes = new ArrayList<>();
receiverNodes.add(SacnNode.getBroadcastNode(universe.getUniverseId()));
logger.debug("using multicast mode to {} for {}", receiverNodes, this.thing.getUID());
}
if (!configuration.localaddress.isEmpty()) {
senderNode = new IpNode(configuration.localaddress);
}
logger.debug("originating address is {} for {}", senderNode, this.thing.getUID());
refreshAlways = configuration.refreshmode.equals("always");
logger.debug("refresh mode set to always: {}", refreshAlways);
updateStatus(ThingStatus.UNKNOWN);
super.updateConfiguration();
logger.debug("updated configuration for sACN/E1.31 bridge {}", this.thing.getUID());
}
@Override
public void initialize() {
logger.debug("initializing sACN/E1.31 bridge {}", this.thing.getUID());
packetTemplate = new SacnPacket(senderUUID);
updateConfiguration();
}
}

View File

@@ -0,0 +1,322 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.handler;
import static org.openhab.binding.dmx.internal.DmxBindingConstants.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.IntStream;
import org.openhab.binding.dmx.internal.DmxBindingConstants.ListenerType;
import org.openhab.binding.dmx.internal.DmxBridgeHandler;
import org.openhab.binding.dmx.internal.DmxThingHandler;
import org.openhab.binding.dmx.internal.Util;
import org.openhab.binding.dmx.internal.ValueSet;
import org.openhab.binding.dmx.internal.action.FadeAction;
import org.openhab.binding.dmx.internal.config.TunableWhiteThingHandlerConfiguration;
import org.openhab.binding.dmx.internal.multiverse.BaseDmxChannel;
import org.openhab.binding.dmx.internal.multiverse.DmxChannel;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link TunableWhiteThingHandler} is responsible for handling commands, which are
* sent to the dimmer.
*
* @author Jan N. Klug - Initial contribution
*/
public class TunableWhiteThingHandler extends DmxThingHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_TUNABLEWHITE);
private final Logger logger = LoggerFactory.getLogger(TunableWhiteThingHandler.class);
private final List<DmxChannel> channels = new ArrayList<>();
private final List<Integer> currentValues = new ArrayList<>();
private PercentType currentBrightness = PercentType.ZERO;
private PercentType currentColorTemperature = new PercentType(50);
private ValueSet turnOnValue = new ValueSet(0, -1, DmxChannel.MAX_VALUE);
private ValueSet turnOffValue = new ValueSet(0, -1, DmxChannel.MIN_VALUE);
private int fadeTime = 0, dimTime = 0;
private boolean dynamicTurnOnValue = false;
private boolean isDimming = false;
public TunableWhiteThingHandler(Thing dimmerThing) {
super(dimmerThing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.trace("received command {} in channel {}", command, channelUID);
ValueSet targetValueSet = new ValueSet(fadeTime, -1);
switch (channelUID.getId()) {
case CHANNEL_BRIGHTNESS: {
if (command instanceof PercentType || command instanceof DecimalType) {
PercentType brightness = (command instanceof PercentType) ? (PercentType) command
: Util.toPercentValue(((DecimalType) command).intValue());
logger.trace("adding fade to channels in thing {}", this.thing.getUID());
targetValueSet.addValue(Util.toDmxValue(
Util.toDmxValue(brightness) * (100 - currentColorTemperature.intValue()) / 100));
targetValueSet.addValue(
Util.toDmxValue(Util.toDmxValue(brightness) * currentColorTemperature.intValue() / 100));
} else if (command instanceof OnOffType) {
logger.trace("adding {} fade to channels in thing {}", command, this.thing.getUID());
if (((OnOffType) command) == OnOffType.ON) {
targetValueSet = turnOnValue;
} else {
if (dynamicTurnOnValue) {
turnOnValue.clear();
for (DmxChannel channel : channels) {
turnOnValue.addValue(channel.getValue());
}
logger.trace("stored channel values fort next turn-on");
}
targetValueSet = turnOffValue;
}
} else if (command instanceof IncreaseDecreaseType) {
if (isDimming && ((IncreaseDecreaseType) command).equals(IncreaseDecreaseType.INCREASE)) {
logger.trace("stopping fade in thing {}", this.thing.getUID());
channels.forEach(DmxChannel::clearAction);
isDimming = false;
return;
} else {
logger.trace("starting {} fade in thing {}", command, this.thing.getUID());
targetValueSet = ((IncreaseDecreaseType) command).equals(IncreaseDecreaseType.INCREASE)
? turnOnValue
: turnOffValue;
targetValueSet.setFadeTime(dimTime);
isDimming = true;
}
} else if (command instanceof RefreshType) {
logger.trace("sending update on refresh to channel {}:brightness", this.thing.getUID());
currentValues.set(0, channels.get(0).getValue());
currentValues.set(1, channels.get(1).getValue());
updateCurrentBrightnessAndTemperature();
updateState(channelUID, currentBrightness);
return;
} else {
logger.debug("command {} not supported in channel {}:brightness", command.getClass(),
this.thing.getUID());
return;
}
break;
}
case CHANNEL_BRIGHTNESS_CW:
if (command instanceof RefreshType) {
logger.trace("sending update on refresh to channel {}:brightness_cw", this.thing.getUID());
currentValues.set(0, channels.get(0).getValue());
updateState(channelUID, Util.toPercentValue(currentValues.get(0)));
return;
} else {
logger.debug("command {} not supported in channel {}:brightness_cw", command.getClass(),
this.thing.getUID());
return;
}
case CHANNEL_BRIGHTNESS_WW:
if (command instanceof RefreshType) {
logger.trace("sending update on refresh to channel {}:brightness_ww", this.thing.getUID());
currentValues.set(1, channels.get(1).getValue());
updateState(channelUID, Util.toPercentValue(currentValues.get(1)));
return;
} else {
logger.debug("command {} not supported in channel {}:brightness_ww", command.getClass(),
this.thing.getUID());
return;
}
case CHANNEL_COLOR_TEMPERATURE: {
if (command instanceof PercentType) {
PercentType colorTemperature = (PercentType) command;
targetValueSet.addValue(Util.toDmxValue(
Util.toDmxValue(currentBrightness) * (100 - colorTemperature.intValue()) / 100));
targetValueSet.addValue(
Util.toDmxValue(Util.toDmxValue(currentBrightness) * colorTemperature.intValue() / 100));
} else if (command instanceof RefreshType) {
logger.trace("sending update on refresh to channel {}:color_temperature", this.thing.getUID());
currentValues.set(0, channels.get(0).getValue());
currentValues.set(1, channels.get(1).getValue());
updateCurrentBrightnessAndTemperature();
updateState(channelUID, currentColorTemperature);
return;
} else {
logger.debug("command {} not supported in channel {}:color_temperature", command.getClass(),
this.thing.getUID());
return;
}
break;
}
default:
logger.debug("channel {} not supported in thing {}", channelUID.getId(), this.thing.getUID());
return;
}
final ValueSet valueSet = targetValueSet;
IntStream.range(0, channels.size()).forEach(i -> {
channels.get(i).setChannelAction(new FadeAction(valueSet.getFadeTime(), channels.get(i).getValue(),
valueSet.getValue(i), valueSet.getHoldTime()));
});
}
@Override
public void initialize() {
Bridge bridge = getBridge();
DmxBridgeHandler bridgeHandler;
if (bridge == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "no bridge assigned");
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
return;
} else {
bridgeHandler = (DmxBridgeHandler) bridge.getHandler();
if (bridgeHandler == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "no bridge handler available");
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
return;
}
}
TunableWhiteThingHandlerConfiguration configuration = getConfig()
.as(TunableWhiteThingHandlerConfiguration.class);
if (configuration.dmxid.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"DMX channel configuration missing");
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
return;
}
try {
List<BaseDmxChannel> configChannels = BaseDmxChannel.fromString(configuration.dmxid,
bridgeHandler.getUniverseId());
logger.trace("found {} channels in {}", configChannels.size(), this.thing.getUID());
for (BaseDmxChannel channel : configChannels) {
channels.add(bridgeHandler.getDmxChannel(channel, this.thing));
}
} catch (IllegalArgumentException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
return;
}
currentValues.add(DmxChannel.MIN_VALUE);
currentValues.add(DmxChannel.MIN_VALUE);
fadeTime = configuration.fadetime;
logger.trace("setting fadeTime to {} ms in {}", fadeTime, this.thing.getUID());
dimTime = configuration.dimtime;
logger.trace("setting dimTime to {} ms in {}", fadeTime, this.thing.getUID());
String turnOnValueString = String.valueOf(fadeTime) + ":" + configuration.turnonvalue + ":-1";
ValueSet turnOnValue = ValueSet.fromString(turnOnValueString);
if (turnOnValue.size() % 2 == 0) {
this.turnOnValue = turnOnValue;
logger.trace("set turnonvalue to {} in {}", turnOnValue, this.thing.getUID());
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "turn-on value malformed");
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
return;
}
this.turnOnValue.setFadeTime(fadeTime);
dynamicTurnOnValue = configuration.dynamicturnonvalue;
String turnOffValueString = String.valueOf(fadeTime) + ":" + configuration.turnoffvalue + ":-1";
ValueSet turnOffValue = ValueSet.fromString(turnOffValueString);
if (turnOffValue.size() % 2 == 0) {
this.turnOffValue = turnOffValue;
logger.trace("set turnoffvalue to {} in {}", turnOffValue, this.thing.getUID());
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "turn-off value malformed");
dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
return;
}
this.turnOffValue.setFadeTime(fadeTime);
// register feedback listeners
channels.get(0).addListener(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS_CW), this,
ListenerType.VALUE);
channels.get(1).addListener(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS_WW), this,
ListenerType.VALUE);
if (bridge.getStatus().equals(ThingStatus.ONLINE)) {
updateStatus(ThingStatus.ONLINE);
dmxHandlerStatus = ThingStatusDetail.NONE;
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
}
@Override
public void dispose() {
if (!channels.isEmpty()) {
channels.get(0).removeListener(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS_CW));
channels.get(1).removeListener(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS_WW));
}
Bridge bridge = getBridge();
if (bridge != null) {
DmxBridgeHandler bridgeHandler = (DmxBridgeHandler) bridge.getHandler();
if (bridgeHandler != null) {
bridgeHandler.unregisterDmxChannels(this.thing);
logger.debug("removing {} channels from {}", channels.size(), this.thing.getUID());
}
}
channels.clear();
currentValues.clear();
}
@Override
public void updateChannelValue(ChannelUID channelUID, int value) {
updateState(channelUID, Util.toPercentValue(value));
switch (channelUID.getId()) {
case CHANNEL_BRIGHTNESS_CW:
currentValues.set(0, value);
break;
case CHANNEL_BRIGHTNESS_WW:
currentValues.set(1, value);
break;
default:
logger.debug("don't know how to handle {} in tunable white type", channelUID.getId());
return;
}
updateCurrentBrightnessAndTemperature();
updateState(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS), currentBrightness);
updateState(new ChannelUID(this.thing.getUID(), CHANNEL_COLOR_TEMPERATURE), currentColorTemperature);
logger.trace("received update {} for channel {}, resulting in b={}, ct={}", value, channelUID,
currentBrightness, currentColorTemperature);
}
private void updateCurrentBrightnessAndTemperature() {
currentBrightness = Util.toPercentValue(Util.toDmxValue(currentValues.get(0) + currentValues.get(1)));
if (currentBrightness != PercentType.ZERO) {
currentColorTemperature = new PercentType(
100 * currentValues.get(1) / (currentValues.get(0) + currentValues.get(1)));
}
}
}

View File

@@ -0,0 +1,141 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.multiverse;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.openhab.binding.dmx.internal.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link BaseDmxChannel} represents a basic DMX channel
*
* @author Jan N. Klug - Initial contribution
*/
public class BaseDmxChannel implements Comparable<BaseDmxChannel> {
public static final int MIN_CHANNEL_ID = 1;
public static final int MAX_CHANNEL_ID = 512;
protected static final Pattern CHANNEL_PATTERN = Pattern.compile("^(\\d*(?=:))?:?(\\d*)\\/?(\\d*)?$");
// this static declaration is needed because of the static fromString method
private static final Logger LOGGER = LoggerFactory.getLogger(BaseDmxChannel.class);
private int universeId;
private final int dmxChannelId;
/**
* BaseChannel object
*
* @param universeId integer for DMX universe
* @param dmxChannelId integer for DMX channel
*/
public BaseDmxChannel(int universeId, int dmxChannelId) {
this.universeId = universeId;
this.dmxChannelId = Util.coerceToRange(dmxChannelId, MIN_CHANNEL_ID, MAX_CHANNEL_ID, LOGGER, "channelId");
}
/**
* copy constructor
*
* @param dmxChannel a BaseChannel object
*/
public BaseDmxChannel(BaseDmxChannel dmxChannel) {
this.universeId = dmxChannel.getUniverseId();
this.dmxChannelId = dmxChannel.getChannelId();
LOGGER.trace("created DMX channel {} in universe {} ", dmxChannelId, universeId);
}
/**
* get DMX channel
*
* @return a integer for the DMX channel
*/
public int getChannelId() {
return dmxChannelId;
}
/**
* get DMX universe
*
* @return a integer for the DMX universe
*/
public int getUniverseId() {
return universeId;
}
/**
* set the DMX universe id
*
* @param universeId a integer for the new universe
*/
public void setUniverseId(int universeId) {
this.universeId = universeId;
}
@Override
public int compareTo(BaseDmxChannel otherDmxChannel) {
if (otherDmxChannel == null) {
return -1;
}
int universeCompare = new Integer(getUniverseId()).compareTo(new Integer(otherDmxChannel.getUniverseId()));
if (universeCompare == 0) {
return new Integer(getChannelId()).compareTo(new Integer(otherDmxChannel.getChannelId()));
} else {
return universeCompare;
}
}
@Override
public String toString() {
return universeId + ":" + dmxChannelId;
}
/**
* parse a BaseChannel list from string
*
* @param dmxChannelString channel string in format [universe:]channel[/width],...
* @param defaultUniverseId default id to use if universe not specified
* @return a List of BaseChannels
*/
public static List<BaseDmxChannel> fromString(String dmxChannelString, int defaultUniverseId)
throws IllegalArgumentException {
List<BaseDmxChannel> dmxChannels = new ArrayList<>();
Stream.of(dmxChannelString.split(",")).forEach(singleDmxChannelString -> {
int dmxChannelId, dmxChannelWidth;
Matcher channelMatch = CHANNEL_PATTERN.matcher(singleDmxChannelString);
if (channelMatch.matches()) {
final int universeId = (channelMatch.group(1) == null) ? defaultUniverseId
: Integer.valueOf(channelMatch.group(1));
dmxChannelWidth = channelMatch.group(3).equals("") ? 1 : Integer.valueOf(channelMatch.group(3));
dmxChannelId = Integer.valueOf(channelMatch.group(2));
LOGGER.trace("parsed channel string {} to universe {}, id {}, width {}", singleDmxChannelString,
universeId, dmxChannelId, dmxChannelWidth);
IntStream.range(dmxChannelId, dmxChannelId + dmxChannelWidth)
.forEach(c -> dmxChannels.add(new BaseDmxChannel(universeId, c)));
} else {
throw new IllegalArgumentException("invalid channel definition" + singleDmxChannelString);
}
});
return dmxChannels;
}
}

View File

@@ -0,0 +1,359 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.multiverse;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.openhab.binding.dmx.internal.DmxBindingConstants.ListenerType;
import org.openhab.binding.dmx.internal.DmxThingHandler;
import org.openhab.binding.dmx.internal.Util;
import org.openhab.binding.dmx.internal.action.ActionState;
import org.openhab.binding.dmx.internal.action.BaseAction;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link DmxChannel} extends {@link BaseDmxChannel} with actions and values
* handlers.
*
* @author Jan N. Klug - Initial contribution
* @author Davy Vanherbergen - Initial contribution
*/
public class DmxChannel extends BaseDmxChannel {
public static final int MIN_VALUE = 0;
public static final int MAX_VALUE = 255;
private final Logger logger = LoggerFactory.getLogger(DmxChannel.class);
private int value = MIN_VALUE;
private int suspendedValue = MIN_VALUE;
private int lastStateValue = -1;
private boolean isSuspended = false;
private int refreshTime = 0;
private long lastStateTimestamp = 0;
private final List<BaseAction> actions = new ArrayList<>();
private final List<BaseAction> suspendedActions = new ArrayList<>();
private final List<Thing> registeredThings = new ArrayList<>();
private final Map<ChannelUID, DmxThingHandler> onOffListeners = new HashMap<>();
private final Map<ChannelUID, DmxThingHandler> valueListeners = new HashMap<>();
private Entry<ChannelUID, DmxThingHandler> actionListener = null;
public DmxChannel(int universeId, int dmxChannelId, int refreshTime) {
super(universeId, dmxChannelId);
this.refreshTime = refreshTime;
}
public DmxChannel(BaseDmxChannel dmxChannel, int refreshTime) {
super(dmxChannel);
this.refreshTime = refreshTime;
}
/**
* register a thing with this channel
*
* @param thing a Thing object
*/
public void registerThing(Thing thing) {
if (!registeredThings.contains(thing)) {
logger.debug("registering {} for DMX Channel {}", thing, this);
registeredThings.add(thing);
}
}
/**
* unregister a thing from this object
*
* @param thing a Thing object
*/
public void unregisterThing(Thing thing) {
if (registeredThings.contains(thing)) {
logger.debug("removing {} from DMX Channel {}", thing, this);
registeredThings.remove(thing);
}
}
/**
* check if DMX Channel has any registered objects
*
* @return true or false
*/
public boolean hasRegisteredThings() {
return !registeredThings.isEmpty();
}
/**
* set a DMX channel value
*
* @param value Integer value (0-255)
*/
public void setValue(int value) {
this.value = Util.toDmxValue(value) << 8;
logger.trace("set dmx channel {} to value {}", this, this.value >> 8);
}
/**
* set a DMX channel value
*
* @param value PercentType (0-100)
*/
public void setValue(PercentType value) {
this.value = Util.toDmxValue(value) << 8;
logger.trace("set dmx channel {} to value {}", this, this.value >> 8);
}
/**
* get the value of this DMX channel
*
* @return value as Integer (0-255)
*/
public int getValue() {
return Util.toDmxValue(value >> 8);
}
/**
* get the high resolution value of this DMX channel
*
* @return value as Integer (0-65535)
*/
public int getHiResValue() {
return value;
}
/**
* suspends current value and actions
*/
public synchronized void suspendAction() {
if (isSuspended) {
logger.info("second suspend for actions in DMX channel {}, previous will be lost", this);
} else {
logger.trace("suspending actions and value for channel {}", this);
}
suspendedValue = value;
suspendedActions.clear();
if (hasRunningActions()) {
suspendedActions.addAll(actions);
}
isSuspended = true;
}
/**
* resumes previously suspended actions
*/
public synchronized void resumeAction() throws IllegalStateException {
if (isSuspended) {
clearAction();
if (!suspendedActions.isEmpty()) {
actions.addAll(suspendedActions);
suspendedActions.clear();
logger.trace("resuming suspended actions for DMX channel {}", this);
} else {
value = suspendedValue;
logger.trace("resuming suspended value for DMX channel {}", this);
}
isSuspended = false;
} else {
throw new IllegalStateException("trying to resume actions in non-suspended DMX channel " + this.toString());
}
}
/**
* check suspended state
*
* @return true or false
*/
public boolean isSuspended() {
return isSuspended;
}
/**
* clear all running actions
*/
public synchronized void clearAction() {
logger.trace("clearing all actions for DMX channel {}", this);
actions.clear();
// remove action listener
if (actionListener != null) {
actionListener.getValue().updateSwitchState(actionListener.getKey(), OnOffType.OFF);
actionListener = null;
}
}
/**
* Replace the current list of channel actions with the provided one.
*
* @param channelAction action for this channel.
*/
public synchronized void setChannelAction(BaseAction channelAction) {
clearAction();
actions.add(channelAction);
logger.trace("set action {} for DMX channel {}", channelAction, this);
}
/**
* Add a channel action to the current list of channel actions.
*
* @param channelAction action for this channel.
*/
public synchronized void addChannelAction(BaseAction channelAction) {
actions.add(channelAction);
logger.trace("added action {} to channel {} (total {} actions)", channelAction, this, actions.size());
}
/**
* @return true if there are running actions
*/
public boolean hasRunningActions() {
return !actions.isEmpty();
}
/**
* Move to the next action in the action chain. This method is used by
* automatic chains and to manually move to the next action if actions are
* set as indefinite (e.g. endless hold). This allows the user to toggle
* through a fixed set of values.
*/
public synchronized void switchToNextAction() {
// push action to the back of the action list
BaseAction action = actions.get(0);
actions.remove(0);
action.reset();
actions.add(action);
logger.trace("switching to next action {} on channel {}", actions.get(0), this);
}
/**
* Get the new value for this channel as determined by active actions or the
* current value.
*
* @param calculationTime UNIX timestamp
* @return value 0-255
*/
public synchronized Integer getNewValue(long calculationTime) {
return (getNewHiResValue(calculationTime) >> 8);
}
/**
* Get the new value for this channel as determined by active actions or the
* current value.
*
* @param calculationTime UNIX timestamp
* @return value 0-65535
*/
public synchronized Integer getNewHiResValue(long calculationTime) {
if (hasRunningActions()) {
logger.trace("checking actions, list is {}", actions);
BaseAction action = actions.get(0);
value = action.getNewValue(this, calculationTime);
if (action.getState() == ActionState.COMPLETED && hasRunningActions()) {
switchToNextAction();
} else if (action.getState() == ActionState.COMPLETEDFINAL) {
clearAction();
}
}
// send updates not more than once in a second, and only on value change
if ((lastStateValue != value) && (calculationTime - lastStateTimestamp > refreshTime)) {
// notify value listeners if value changed
for (Entry<ChannelUID, DmxThingHandler> listener : valueListeners.entrySet()) {
int dmxValue = Util.toDmxValue(value >> 8);
(listener.getValue()).updateChannelValue(listener.getKey(), dmxValue);
logger.trace("sending VALUE={} (raw={}) status update to listener {} ({})", dmxValue, value,
listener.getValue(), listener.getKey());
}
// notify on/off listeners if on/off state changed
if ((lastStateValue == 0) || (value == 0)) {
OnOffType state = (value == 0) ? OnOffType.OFF : OnOffType.ON;
for (Entry<ChannelUID, DmxThingHandler> listener : onOffListeners.entrySet()) {
(listener.getValue()).updateSwitchState(listener.getKey(), state);
logger.trace("sending ONOFF={} (raw={}), status update to listener {}", state, value,
listener.getKey());
}
}
lastStateValue = value;
lastStateTimestamp = calculationTime;
}
return value;
}
/**
* add a channel listener for state updates
*
* @param thingChannel the channel the listener is linked to
* @param listener the listener itself
*/
public void addListener(ChannelUID thingChannel, DmxThingHandler listener, ListenerType type) {
switch (type) {
case VALUE:
if (valueListeners.containsKey(thingChannel)) {
logger.trace("VALUE listener {} already exists in channel {}", thingChannel, this);
} else {
valueListeners.put(thingChannel, listener);
logger.debug("adding VALUE listener {} to channel {}", thingChannel, this);
}
break;
case ACTION:
if (actionListener != null) {
logger.info("replacing ACTION listener {} with {} in channel {}", actionListener.getValue(),
listener, this);
} else {
logger.debug("adding ACTION listener {} in channel {}", listener, this);
}
actionListener = new AbstractMap.SimpleEntry<>(thingChannel, listener);
default:
}
}
/**
* remove listener from channel
*
* @param thingChannel the channel that shall no longer receive updates
*/
public void removeListener(ChannelUID thingChannel) {
boolean foundListener = false;
if (onOffListeners.containsKey(thingChannel)) {
onOffListeners.remove(thingChannel);
foundListener = true;
logger.debug("removing ONOFF listener {} from DMX channel {}", thingChannel, this);
}
if (valueListeners.containsKey(thingChannel)) {
valueListeners.remove(thingChannel);
foundListener = true;
logger.debug("removing VALUE listener {} from DMX channel {}", thingChannel, this);
}
if (actionListener != null && actionListener.getKey().equals(thingChannel)) {
actionListener = null;
foundListener = true;
logger.debug("removing ACTION listener {} from DMX channel {}", thingChannel, this);
}
if (!foundListener) {
logger.trace("listener {} not found in DMX channel {}", thingChannel, this);
}
}
}

View File

@@ -0,0 +1,259 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.multiverse;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import org.openhab.core.thing.Thing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link Universe} represents a single DMX universes with all its channels and provides a buffer for sending by the
* bridges
*
* @author Jan N. Klug - Initial contribution
*/
public class Universe {
public static final int MIN_UNIVERSE_SIZE = 32;
public static final int MAX_UNIVERSE_SIZE = 512;
public static final int DEFAULT_REFRESH_TIME = 1000;
private final Logger logger = LoggerFactory.getLogger(Universe.class);
private final ReentrantLock universeLock = new ReentrantLock();
private int universeId;
private int bufferSize = MIN_UNIVERSE_SIZE;
private final short[] buffer = new short[MAX_UNIVERSE_SIZE];
private final short[] cie1931Curve = new short[DmxChannel.MAX_VALUE << 8 + 1];
private long bufferChanged;
private int refreshTime = DEFAULT_REFRESH_TIME;
private final List<DmxChannel> channels = new ArrayList<>();
private final List<Integer> applyCurve = new ArrayList<>();
/**
* universe constructor
*
* @param universeId the universe id
*/
public Universe(int universeId) {
this.universeId = universeId;
fillDimCurveLookupTable();
}
/**
* universe constructor with default universe 1
*/
public Universe() {
this(1);
}
/**
* register a channel in the universe, create if not existing
*
* @param channel the channel represented by a {@link BaseDmxChannel} object
* @param thing the thing to register this channel to
* @return a full featured channel
*/
public synchronized DmxChannel registerChannel(BaseDmxChannel baseChannel, Thing thing) {
for (DmxChannel channel : channels) {
if (baseChannel.compareTo(channel) == 0) {
logger.trace("returning existing channel {}", channel);
channel.registerThing(thing);
return channel;
}
}
DmxChannel channel = new DmxChannel(baseChannel, refreshTime);
addChannel(channel);
channel.registerThing(thing);
logger.debug("creating and returning channel {}", channel);
return channel;
}
/**
* unregister thing from a channel (deletes channel if not used anymore)
*
* @param thing the thing to unregister
*/
public synchronized void unregisterChannels(Thing thing) {
universeLock.lock();
try {
Iterator<DmxChannel> channelIterator = channels.iterator();
while (channelIterator.hasNext()) {
DmxChannel channel = channelIterator.next();
channel.unregisterThing(thing);
if (!channel.hasRegisteredThings()) {
channelIterator.remove();
logger.trace("Removing channel {}, no more things", channel);
}
}
} finally {
universeLock.unlock();
}
}
/**
* add an existing channel to this universe
*
* @param channel a {@link DmxChannel} object within this universe
*/
private void addChannel(DmxChannel channel) throws IllegalArgumentException {
if (universeId == channel.getUniverseId()) {
universeLock.lock();
try {
channels.add(channel);
if (channel.getChannelId() > bufferSize) {
bufferSize = channel.getChannelId();
}
} finally {
universeLock.unlock();
}
} else {
throw new IllegalArgumentException(
String.format("Adding channel %s to universe %d not possible", channel.toString(), universeId));
}
}
/**
* get the timestamp of the last buffer change
*
* @return timestamp
*/
public long getLastBufferChanged() {
return bufferChanged;
}
/**
* get size of the buffer
*
* @return value between {@link MIN_UNIVERSE_SIZE} and 512
*/
public int getBufferSize() {
return bufferSize;
}
/**
* get universe id
*
* @return this universe DMX id
*/
public int getUniverseId() {
return universeId;
}
/**
* change universe id
*
* @param universeId new universe id
*/
public void rename(int universeId) {
logger.debug("Renaming universe {} to {}", this.universeId, universeId);
this.universeId = universeId;
for (DmxChannel channel : channels) {
channel.setUniverseId(universeId);
}
}
/**
* calculate this universe buffer (run all channel actions) for a given time
*
* @param time the timestamp used for calculation
*/
public void calculateBuffer(long time) {
universeLock.lock();
try {
for (DmxChannel channel : channels) {
logger.trace("calculating new value for {}", channel);
int channelId = channel.getChannelId();
int vx = channel.getNewHiResValue(time);
int value;
if (applyCurve.contains(channelId)) {
value = cie1931Curve[vx];
} else {
value = vx >> 8;
}
if (buffer[channelId - 1] != value) {
buffer[channelId - 1] = (short) value;
bufferChanged = time;
}
}
} finally {
universeLock.unlock();
}
}
/**
* get the full universe buffer
*
* @return byte array with channel values
*/
public byte[] getBuffer() {
byte[] b = new byte[bufferSize];
universeLock.lock();
try {
for (int i = 0; i < bufferSize; i++) {
b[i] = (byte) buffer[i];
}
} finally {
universeLock.unlock();
}
return b;
}
/**
* set list of channels that should use the LED dim curve
*
* @param listString
*/
public void setDimCurveChannels(String listString) {
applyCurve.clear();
for (BaseDmxChannel channel : BaseDmxChannel.fromString(listString, universeId)) {
applyCurve.add(channel.getChannelId());
}
logger.debug("applying dim curve in universe {} to channels {}", universeId, applyCurve);
}
/**
* calculate dim curve table for fast lookup
*/
private void fillDimCurveLookupTable() {
// formula taken from: Poynton, C.A.: “Gamma” and its Disguises: The Nonlinear Mappings of
// Intensity in Perception, CRTs, Film and Video, SMPTE Journal Dec. 1993, pp. 1099 - 1108
// inverted
int maxValue = DmxChannel.MAX_VALUE << 8;
for (int i = 0; i <= maxValue; i++) {
float lLn = ((float) i) / maxValue;
if (lLn <= 0.08) {
cie1931Curve[i] = (short) Math.round(DmxChannel.MAX_VALUE * lLn / 9.033);
} else {
cie1931Curve[i] = (short) Math.round(DmxChannel.MAX_VALUE * Math.pow((lLn + 0.16) / 1.16, 3));
}
}
}
/**
* set channel refresh time
*
* @param refreshTime time in ms between state updates for a DMX channel
*/
public void setRefreshTime(int refreshTime) {
this.refreshTime = refreshTime;
}
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="dmx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>DMX Binding</name>
<description>This is the binding for DMX.</description>
<author>Jan N. Klug</author>
</binding:binding>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="dmx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- ArtNet Bridge -->
<bridge-type id="artnet-bridge">
<label>ArtNet Bridge</label>
<description>The ArtNet bridge represents a single DMX universe connected via ArtNet, only unicast</description>
<channels>
<channel id="mute" typeId="mute"/>
</channels>
<config-description>
<parameter name="address" type="text">
<label>Receiver Address(es)</label>
<description>Network addresses of ArtNet receivers, format: address[:port][, address[:port], ...]. Default port is
6454.</description>
<required>true</required>
</parameter>
<parameter name="localaddress" type="text">
<label>Local Network Address</label>
<description>Network address of the sending host, format: address[:port]. Default port is 0 (random)</description>
<advanced>true</advanced>
</parameter>
<parameter name="universe" type="integer" min="0" max="32767">
<label>DMX Universe</label>
<description>ID of DMX universe (0-32767)</description>
<default>0</default>
</parameter>
<parameter name="refreshmode" type="text">
<label>Refresh Mode</label>
<description>Suppress re-transmission and refresh every 800ms or send every packet.</description>
<options>
<option value="always">Always</option>
<option value="standard">Standard</option>
</options>
<default>standard</default>
<advanced>true</advanced>
</parameter>
<parameter name="applycurve" type="text">
<label>Apply Curve</label>
<description>List of channels that should use LED dim curve. Format is channel[,channel, ...] or channel[/width].</description>
<advanced>true</advanced>
</parameter>
<parameter name="refreshrate" type="decimal" min="0" max="100">
<description>DMX refresh rate in Hz (0=disable output)</description>
<default>30</default>
<advanced>true</advanced>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="dmx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Brightness Channel -->
<channel-type id="brightness">
<item-type>Dimmer</item-type>
<label>Brightness</label>
<description>Allows to send a brightness value to the thing. If the thing is associated with more than one DMX
channel, the values is used for ALL DMX channels.</description>
<category>DimmableLight</category>
<tags>
<tag>Lighting</tag>
</tags>
</channel-type>
<!-- Color Temperature -->
<channel-type id="color_temperature">
<item-type>Dimmer</item-type>
<label>Color Temperature</label>
<description>Relative intensity of two adjacent DMX channels</description>
<category>ColorLight</category>
<tags>
<tag>Lighting</tag>
</tags>
</channel-type>
<!-- Color Channel -->
<channel-type id="color">
<item-type>Color</item-type>
<label>Color</label>
<description>Allows to send a color value to the thing. If the thing is associated with more than one DMX channel,
color will be re-use in 3-channel groups.</description>
<category>ColorLight</category>
<tags>
<tag>Lighting</tag>
</tags>
</channel-type>
<!-- Switch Channel -->
<channel-type id="switch">
<item-type>Switch</item-type>
<label>Switch</label>
<description>Allows to switch a thing ON or OFF.</description>
<category>Light</category>
</channel-type>
<!-- Control Channel -->
<channel-type id="control">
<item-type>String</item-type>
<label>Control</label>
<description>Change Fade Configuration</description>
<category>Light</category>
</channel-type>
<!-- Mute Channel for bridges -->
<channel-type id="mute">
<item-type>Switch</item-type>
<label>Mute</label>
<description>Mutes the DMX output of the Bridge</description>
<category>Light</category>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="dmx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Dimmer -->
<thing-type id="chaser">
<supported-bridge-type-refs>
<bridge-type-ref id="artnet-bridge"/>
<bridge-type-ref id="lib485-bridge"/>
<bridge-type-ref id="sacn-bridge"/>
</supported-bridge-type-refs>
<label>DMX Chaser</label>
<description>A single/multi-channel chaser</description>
<channels>
<channel id="switch" typeId="switch"/>
<channel id="control" typeId="control"/>
</channels>
<config-description>
<parameter name="dmxid" type="text">
<label>DMX Channel Configuration</label>
<description>Format is channel[,channel, ...] or channel[/width]</description>
<required>true</required>
</parameter>
<parameter name="resumeafter" type="boolean">
<label>Resume After Finish</label>
<description>resume old actions after this completes</description>
<default>false</default>
<required>false</required>
</parameter>
<parameter name="steps" type="text">
<label>Step Configuration</label>
<description>fadeTime:value[, ...]:holdTime </description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="dmx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Dimmer -->
<thing-type id="color">
<supported-bridge-type-refs>
<bridge-type-ref id="artnet-bridge"/>
<bridge-type-ref id="lib485-bridge"/>
<bridge-type-ref id="sacn-bridge"/>
</supported-bridge-type-refs>
<label>DMX Color (RGB) Dimmer</label>
<description>A RGB capable dimmer</description>
<channels>
<channel id="brightness_r" typeId="brightness"/>
<channel id="brightness_g" typeId="brightness"/>
<channel id="brightness_b" typeId="brightness"/>
<channel id="color" typeId="color"/>
</channels>
<config-description>
<parameter name="dmxid" type="text">
<label>DMX Channel Configuration</label>
<description>Format is channel[,channel, ...] or channel[/width], has to be multiple of three</description>
<required>true</required>
</parameter>
<parameter name="fadetime" type="integer" min="0">
<label>Fade Time</label>
<description>Fade time in ms for changing values</description>
<default>0</default>
</parameter>
<parameter name="dimtime" type="integer" min="0">
<label>Dim Time</label>
<description>Time in ms for dimming from 0-100%</description>
<default>0</default>
</parameter>
<parameter name="turnonvalue" type="text">
<label>Turn-On Value(s)</label>
<description>Format is "value[, value, ...]", has to be a multiple of three. If less values than channels are
defined, they are reused from the beginning.</description>
<required>false</required>
<advanced>true</advanced>
</parameter>
<parameter name="turnoffvalue" type="text">
<label>Turn-Off Value(s)</label>
<description>Format is "value[, value, ...]", has to be a multiple of three. If less values than channels are
defined, they are reused from the beginning.</description>
<advanced>true</advanced>
</parameter>
<parameter name="dynamicturnonvalue" type="boolean">
<label>Dynamic Turn-On Value</label>
<description>If enabled, values are stored on OFF command and restored on ON command</description>
<required>false</required>
<default>false</default>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="dmx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Dimmer -->
<thing-type id="dimmer">
<supported-bridge-type-refs>
<bridge-type-ref id="artnet-bridge"/>
<bridge-type-ref id="lib485-bridge"/>
<bridge-type-ref id="sacn-bridge"/>
</supported-bridge-type-refs>
<label>DMX Dimmer</label>
<description>A single/multi-channel dimmer</description>
<channels>
<channel id="brightness" typeId="brightness"/>
<channel id="switch" typeId="switch"/>
</channels>
<config-description>
<parameter name="dmxid" type="text">
<label>DMX Channel Configuration</label>
<description>Format is channel[,channel, ...] or channel[/width]</description>
<required>true</required>
</parameter>
<parameter name="fadetime" type="integer" min="0">
<label>Fade Time</label>
<description>Fade time in ms for changing values</description>
<default>0</default>
</parameter>
<parameter name="dimtime" type="integer" min="0">
<label>Dim Time</label>
<description>Time in ms for dimming from 0-100%</description>
<default>0</default>
</parameter>
<parameter name="turnonvalue" type="text">
<label>Turn-On Value(s)</label>
<description>Format is "value[, value, ...]". If less values than channels are defined, they are reused from the
beginning.
</description>
<required>false</required>
<advanced>true</advanced>
</parameter>
<parameter name="turnoffvalue" type="text">
<label>Turn-Off Value(s)</label>
<description>Format is "value[, value, ...]". If less values than channels are defined, they are reused from the
beginning.
</description>
<advanced>true</advanced>
</parameter>
<parameter name="dynamicturnonvalue" type="boolean">
<label>Dynamic Turn-On Value</label>
<description>If enabled, values are stored on OFF command and restored on ON command</description>
<required>false</required>
<default>false</default>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="dmx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Lib485 Bridge -->
<bridge-type id="lib485-bridge">
<label>Lib485 Bridge</label>
<description>The Lib485 bridge represents a single DMX universe connected via Lib485</description>
<channels>
<channel id="mute" typeId="mute"/>
</channels>
<config-description>
<parameter name="address" type="text">
<label>Network Address</label>
<description>Network address of bridge, format: address[:port]. Default port is 9020.</description>
<required>true</required>
<default>localhost</default>
</parameter>
<parameter name="applycurve" type="text">
<label>Apply Curve</label>
<description>List of channels that should use LED dim curve. Format is channel[,channel, ...] or channel[/width].</description>
<advanced>true</advanced>
</parameter>
<parameter name="refreshrate" type="decimal" min="0" max="100">
<description>DMX refresh rate in Hz (0=disable output)</description>
<default>30</default>
<advanced>true</advanced>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="dmx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- sACN/E1.31 Bridge -->
<bridge-type id="sacn-bridge">
<label>sACN/E1.31 Bridge</label>
<description>The sACN/E1.31 bridge represents a single DMX universe connected via sACN/E1.31</description>
<channels>
<channel id="mute" typeId="mute"/>
</channels>
<config-description>
<parameter name="mode" type="text">
<context></context>
<label>Transmission Mode</label>
<description>Set UDP mode to multicast (default)/unicast</description>
<required>true</required>
<options>
<option value="unicast">Unicast</option>
<option value="multicast">Multicast</option>
</options>
<default>multicast</default>
</parameter>
<parameter name="address" type="text">
<label>Receiver Address(es)</label>
<description>Network addresses of sACN/E1.31 receivers, format: address[:port][, address[:port], ...]. Default port
is 5568.</description>
<required>false</required>
</parameter>
<parameter name="localaddress" type="text">
<label>Local Network Address</label>
<description>Network address of the sending host, format: address[:port]. Default port is 0 (random)</description>
<required>false</required>
<advanced>true</advanced>
</parameter>
<parameter name="universe" type="integer" min="1" max="63999">
<label>DMX Universe</label>
<description>ID of DMX universe (1-63999)</description>
<default>1</default>
<required>false</required>
</parameter>
<parameter name="refreshmode" type="text">
<label>Refresh Mode</label>
<description>Suppress re-transmission and refresh every 800ms or send every packet.</description>
<required>false</required>
<options>
<option value="always">Always</option>
<option value="standard">Standard</option>
</options>
<default>standard</default>
<advanced>true</advanced>
</parameter>
<parameter name="applycurve" type="text">
<label>Apply Curve</label>
<description>List of channels that should use LED dim curve. Format is channel[,channel, ...] or channel[/width].</description>
<required>false</required>
<advanced>true</advanced>
</parameter>
<parameter name="refreshrate" type="decimal" min="0" max="100">
<description>DMX refresh rate in Hz</description>
<required>false</required>
<default>30</default>
<advanced>true</advanced>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="dmx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Dimmer -->
<thing-type id="tunablewhite">
<supported-bridge-type-refs>
<bridge-type-ref id="artnet-bridge"/>
<bridge-type-ref id="lib485-bridge"/>
<bridge-type-ref id="sacn-bridge"/>
</supported-bridge-type-refs>
<label>DMX Tunable White Dimmer</label>
<description>A tunable white capable dimmer</description>
<channels>
<channel id="brightness" typeId="brightness"/>
<channel id="brightness_cw" typeId="brightness"/>
<channel id="brightness_ww" typeId="brightness"/>
<channel id="color_temperature" typeId="color_temperature"/>
</channels>
<config-description>
<parameter name="dmxid" type="text">
<label>DMX Channel Configuration</label>
<description>Format is channel[,channel, ...] or channel[/width], has to be an even number</description>
<required>true</required>
</parameter>
<parameter name="fadetime" type="integer" min="0">
<label>Fade Time</label>
<description>Fade time in ms for changing values</description>
<default>0</default>
</parameter>
<parameter name="dimtime" type="integer" min="0">
<label>Dim Time</label>
<description>Time in ms for dimming from 0-100%</description>
<default>0</default>
</parameter>
<parameter name="turnonvalue" type="text">
<label>Turn-On Value(s)</label>
<description>Format is "value[, value, ...]", has to be a multiple of two. If less values than channels are defined,
they are reused from the beginning.
</description>
<required>false</required>
<advanced>true</advanced>
</parameter>
<parameter name="turnoffvalue" type="text">
<label>Turn-Off Value(s)</label>
<description>Format is "value[, value, ...]", has to be a multiple of two. If less values than channels are defined,
they are reused from the beginning.
</description>
<advanced>true</advanced>
</parameter>
<parameter name="dynamicturnonvalue" type="boolean">
<label>Dynamic Turn-On Value</label>
<description>If enabled, values are stored on OFF command and restored on ON command</description>
<required>false</required>
<default>false</default>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,147 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.openhab.binding.dmx.internal.action.ActionState;
import org.openhab.binding.dmx.internal.action.FadeAction;
import org.openhab.binding.dmx.internal.multiverse.DmxChannel;
/**
* Tests cases FadeAction
*
* @author Jan N. Klug - Initial contribution
*/
public class FadeActionTest {
private static final int testValue = 200;
private static final int testFadeTime = 1000;
private static final int testHoldTime = 1000;
@Test
public void checkWithFadingWithoutHold() {
FadeAction fadeAction = new FadeAction(testFadeTime, testValue, 0);
DmxChannel testChannel = new DmxChannel(0, 1, 0);
testChannel.setValue(0);
long startTime = System.currentTimeMillis();
assertThat(fadeAction.getState(), is(ActionState.WAITING));
assertThat(fadeAction.getNewValue(testChannel, startTime), is(0));
assertThat(fadeAction.getState(), is(ActionState.RUNNING));
assertThat(fadeAction.getNewValue(testChannel, startTime + testFadeTime / 2), is(256 * testValue / 2));
assertThat(fadeAction.getNewValue(testChannel, startTime + 1000), is(256 * testValue));
assertThat(fadeAction.getState(), is(ActionState.COMPLETED));
fadeAction.reset();
assertThat(fadeAction.getState(), is(ActionState.WAITING));
}
@Test
public void checkWithFadingWithHold() {
FadeAction fadeAction = new FadeAction(testFadeTime, testValue, testHoldTime);
DmxChannel testChannel = new DmxChannel(0, 1, 0);
testChannel.setValue(0);
long startTime = System.currentTimeMillis();
assertThat(fadeAction.getState(), is(ActionState.WAITING));
assertThat(fadeAction.getNewValue(testChannel, startTime), is(0));
assertThat(fadeAction.getState(), is(ActionState.RUNNING));
assertThat(fadeAction.getNewValue(testChannel, startTime + testFadeTime / 2), is(256 * testValue / 2));
assertThat(fadeAction.getNewValue(testChannel, startTime + testFadeTime), is(256 * testValue));
assertThat(fadeAction.getState(), is(ActionState.RUNNING));
assertThat(fadeAction.getNewValue(testChannel, startTime + testFadeTime + testHoldTime / 2),
is(256 * testValue));
assertThat(fadeAction.getState(), is(ActionState.RUNNING));
assertThat(fadeAction.getNewValue(testChannel, startTime + testFadeTime + testHoldTime), is(256 * testValue));
assertThat(fadeAction.getState(), is(ActionState.COMPLETED));
fadeAction.reset();
assertThat(fadeAction.getState(), is(ActionState.WAITING));
}
@Test
public void checkWithFadingWithInfiniteHold() {
FadeAction fadeAction = new FadeAction(testFadeTime, testValue, -1);
DmxChannel testChannel = new DmxChannel(0, 1, 0);
testChannel.setValue(0);
long startTime = System.currentTimeMillis();
assertThat(fadeAction.getState(), is(ActionState.WAITING));
assertThat(fadeAction.getNewValue(testChannel, startTime), is(0));
assertThat(fadeAction.getState(), is(ActionState.RUNNING));
assertThat(fadeAction.getNewValue(testChannel, startTime + testFadeTime / 2), is(256 * testValue / 2));
assertThat(fadeAction.getNewValue(testChannel, startTime + testFadeTime), is(256 * testValue));
assertThat(fadeAction.getState(), is(ActionState.COMPLETEDFINAL));
fadeAction.reset();
assertThat(fadeAction.getState(), is(ActionState.WAITING));
}
@Test
public void checkWithoutFadingWithHold() {
FadeAction fadeAction = new FadeAction(0, testValue, testHoldTime);
DmxChannel testChannel = new DmxChannel(0, 1, 0);
testChannel.setValue(0);
long startTime = System.currentTimeMillis();
assertThat(fadeAction.getState(), is(ActionState.WAITING));
assertThat(fadeAction.getNewValue(testChannel, startTime), is(256 * testValue));
assertThat(fadeAction.getState(), is(ActionState.RUNNING));
assertThat(fadeAction.getNewValue(testChannel, startTime + testHoldTime / 2), is(256 * testValue));
assertThat(fadeAction.getState(), is(ActionState.RUNNING));
assertThat(fadeAction.getNewValue(testChannel, startTime + testHoldTime), is(256 * testValue));
assertThat(fadeAction.getState(), is(ActionState.COMPLETED));
fadeAction.reset();
assertThat(fadeAction.getState(), is(ActionState.WAITING));
}
@Test
public void checkWithoutFadingWithoutHold() {
FadeAction fadeAction = new FadeAction(0, testValue, 0);
DmxChannel testChannel = new DmxChannel(0, 1, 0);
testChannel.setValue(0);
long startTime = System.currentTimeMillis();
assertThat(fadeAction.getState(), is(ActionState.WAITING));
assertThat(fadeAction.getNewValue(testChannel, startTime), is(256 * testValue));
assertThat(fadeAction.getState(), is(ActionState.COMPLETED));
fadeAction.reset();
assertThat(fadeAction.getState(), is(ActionState.WAITING));
}
@Test
public void checkWithoutFadingWithInfiniteHold() {
FadeAction fadeAction = new FadeAction(0, testValue, -1);
DmxChannel testChannel = new DmxChannel(0, 1, 0);
testChannel.setValue(0);
long startTime = System.currentTimeMillis();
assertThat(fadeAction.getState(), is(ActionState.WAITING));
assertThat(fadeAction.getNewValue(testChannel, startTime), is(256 * testValue));
assertThat(fadeAction.getState(), is(ActionState.COMPLETEDFINAL));
fadeAction.reset();
assertThat(fadeAction.getState(), is(ActionState.WAITING));
}
}

View File

@@ -0,0 +1,92 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.openhab.binding.dmx.internal.multiverse.DmxChannel;
import org.openhab.core.library.types.PercentType;
/**
* Tests cases Util
*
* @author Jan N. Klug - Initial contribution
*/
public class UtilTest {
@Test
public void coercingOfDmxValues() {
// overrange
int value = Util.toDmxValue(300);
assertThat(value, is(DmxChannel.MAX_VALUE));
// underrange
value = Util.toDmxValue(-1);
assertThat(value, is(DmxChannel.MIN_VALUE));
// inrange
value = Util.toDmxValue(100);
assertThat(value, is(100));
}
@Test
public void conversionString() {
int value = Util.toDmxValue("100");
assertThat(value, is(100));
}
@Test
public void conversionFromPercentType() {
// borders
int value = Util.toDmxValue(new PercentType(100));
assertThat(value, is(255));
value = Util.toDmxValue(new PercentType(0));
assertThat(value, is(0));
// middle
value = Util.toDmxValue(new PercentType(50));
assertThat(value, is(127));
}
@Test
public void conversionToPercentType() {
// borders
PercentType value = Util.toPercentValue(255);
assertThat(value.intValue(), is(100));
value = Util.toPercentValue(0);
assertThat(value.intValue(), is(0));
// middle
value = Util.toPercentValue(127);
assertThat(value.intValue(), is(49));
}
@Test
public void fadeTimeFraction() {
// target already reached
int value = Util.fadeTimeFraction(123, 123, 1000);
assertThat(value, is(0));
// full fade
value = Util.fadeTimeFraction(0, 255, 1000);
assertThat(value, is(1000));
// fraction
value = Util.fadeTimeFraction(100, 155, 2550);
assertThat(value, is(550));
}
}

View File

@@ -0,0 +1,84 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import java.util.List;
import org.junit.Test;
/**
* Tests cases ValueSet
*
* @author Jan N. Klug - Initial contribution
*/
public class ValueSetTest {
@Test
public void fadeAndHoldTime() {
ValueSet valueSet = new ValueSet(100, 200);
assertThat(valueSet.getFadeTime(), is(100));
assertThat(valueSet.getHoldTime(), is(200));
}
@Test
public void valueAndRepetition() {
ValueSet valueSet = new ValueSet(0, 0);
valueSet.addValue(100);
valueSet.addValue(200);
// stored values
assertThat(valueSet.getValue(0), is(100));
assertThat(valueSet.getValue(1), is(200));
// repetitions
assertThat(valueSet.getValue(2), is(100));
assertThat(valueSet.getValue(5), is(200));
}
@Test
public void fromStringDouble() {
ValueSet valueSet = ValueSet.fromString("1000:100,200:-1");
// times
assertThat(valueSet.getFadeTime(), is(1000));
assertThat(valueSet.getHoldTime(), is(-1));
// values
assertThat(valueSet.getValue(0), is(100));
assertThat(valueSet.getValue(1), is(200));
}
@Test
public void fromStringSingle() {
ValueSet valueSet = ValueSet.fromString("1000:100:-1");
// times
assertThat(valueSet.getFadeTime(), is(1000));
assertThat(valueSet.getHoldTime(), is(-1));
// values
assertThat(valueSet.getValue(0), is(100));
}
@Test
public void parseChaserConfig() {
List<ValueSet> config = ValueSet.parseChaseConfig("1000:200,100:100|1000:100:-1");
// step number is correct
assertThat(config.size(), is(2));
}
}

View File

@@ -0,0 +1,104 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.handler;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.openhab.binding.dmx.internal.DmxBindingConstants.*;
import java.util.HashMap;
import java.util.Map;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.test.java.JavaTest;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.thing.binding.builder.BridgeBuilder;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
/**
* Tests cases for {@link org.openhab.binding.dmx.internal.handler.ArtnetBridgeHandler}.
*
* @author Jan N. Klug - Initial contribution
*/
public class ArtnetBridgeHandlerTest extends JavaTest {
private static final String TEST_ADDRESS = "localhost";
private static final int TEST_UNIVERSE = 1;
private final ThingUID BRIDGE_UID_ARTNET = new ThingUID(THING_TYPE_ARTNET_BRIDGE, "artnetbridge");
private final ChannelUID CHANNEL_UID_MUTE = new ChannelUID(BRIDGE_UID_ARTNET, CHANNEL_MUTE);
Map<String, Object> bridgeProperties;
private Bridge bridge;
private ArtnetBridgeHandler bridgeHandler;
@Before
public void setUp() {
bridgeProperties = new HashMap<>();
bridgeProperties.put(CONFIG_ADDRESS, TEST_ADDRESS);
bridgeProperties.put(CONFIG_UNIVERSE, TEST_UNIVERSE);
bridge = BridgeBuilder.create(THING_TYPE_ARTNET_BRIDGE, "artnetbridge").withLabel("Artnet Bridge")
.withChannel(ChannelBuilder.create(CHANNEL_UID_MUTE, "Switch").withType(MUTE_CHANNEL_TYPEUID).build())
.withConfiguration(new Configuration(bridgeProperties)).build();
ThingHandlerCallback mockCallback = mock(ThingHandlerCallback.class);
doAnswer(answer -> {
((Thing) answer.getArgument(0)).setStatusInfo(answer.getArgument(1));
return null;
}).when(mockCallback).statusUpdated(any(), any());
bridgeHandler = new ArtnetBridgeHandler(bridge) {
@Override
protected void validateConfigurationParameters(Map<String, Object> configurationParameters) {
}
};
bridgeHandler.getThing().setHandler(bridgeHandler);
bridgeHandler.setCallback(mockCallback);
bridgeHandler.initialize();
}
@After
public void tearDown() {
bridgeHandler.dispose();
}
@Test
public void assertBridgeStatus() {
waitForAssert(() -> assertEquals(ThingStatus.ONLINE, bridge.getStatusInfo().getStatus()));
}
@Test
public void renamingOfUniverses() {
waitForAssert(() -> assertThat(bridgeHandler.getUniverseId(), is(TEST_UNIVERSE)));
bridgeProperties.replace(CONFIG_UNIVERSE, 2);
bridgeHandler.handleConfigurationUpdate(bridgeProperties);
waitForAssert(() -> assertThat(bridgeHandler.getUniverseId(), is(2)));
bridgeProperties.replace(CONFIG_UNIVERSE, TEST_UNIVERSE);
bridgeHandler.handleConfigurationUpdate(bridgeProperties);
waitForAssert(() -> assertThat(bridgeHandler.getUniverseId(), is(TEST_UNIVERSE)));
}
}

View File

@@ -0,0 +1,202 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.handler;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.openhab.binding.dmx.internal.DmxBindingConstants.*;
import static org.openhab.binding.dmx.test.TestBridgeHandler.THING_TYPE_TEST_BRIDGE;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.Before;
import org.junit.Test;
import org.openhab.binding.dmx.test.AbstractDmxThingTestParent;
import org.openhab.binding.dmx.test.TestBridgeHandler;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.thing.binding.builder.BridgeBuilder;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.binding.builder.ThingBuilder;
/**
* Tests cases for {@link org.openhab.binding.dmx.internal.handler.ChaserThingHandler}.
*
* @author Jan N. Klug - Initial contribution
*/
public class ChaserThingHandlerTest extends AbstractDmxThingTestParent {
private static final String TEST_CHANNEL = "100";
private static final String TEST_STEPS_INFINITE = "1000:100:1000|1000:200:-1";
private static final String TEST_STEPS_REPEAT = "1000:115:1000|1000:210:1000";
private final ThingUID THING_UID_CHASER = new ThingUID(THING_TYPE_CHASER, "testchaser");
private final ChannelUID CHANNEL_UID_SWITCH = new ChannelUID(THING_UID_CHASER, CHANNEL_SWITCH);
Map<String, Object> bridgeProperties;
Map<String, Object> thingProperties;
private Thing chaserThing;
private TestBridgeHandler dmxBridgeHandler;
private ChaserThingHandler chaserThingHandler;
@Before
public void setUp() {
super.setup();
thingProperties = new HashMap<>();
thingProperties.put(CONFIG_DMX_ID, TEST_CHANNEL);
}
@Test
public void testThingStatus() {
thingProperties.put(CONFIG_CHASER_STEPS, TEST_STEPS_INFINITE);
initialize();
assertThingStatus(chaserThing);
}
@Test
public void testThingStatus_noBridge() {
thingProperties.put(CONFIG_CHASER_STEPS, TEST_STEPS_INFINITE);
initialize();
// check that thing is offline if no bridge found
ChaserThingHandler chaserHandlerWithoutBridge = new ChaserThingHandler(chaserThing) {
@Override
protected @Nullable Bridge getBridge() {
return null;
}
};
assertThingStatusWithoutBridge(chaserHandlerWithoutBridge);
}
@Test
public void holdInfiniteChaser() {
initializeTestBridge();
thingProperties.put(CONFIG_CHASER_STEPS, TEST_STEPS_INFINITE);
initialize();
long currentTime = System.currentTimeMillis();
chaserThingHandler.handleCommand(new ChannelUID(chaserThing.getUID(), CHANNEL_SWITCH), OnOffType.ON);
// step I
currentTime = dmxBridgeHandler.calcBuffer(currentTime, 1000);
waitForAssert(() -> assertThat(dmxBridgeHandler.getDmxChannelValue(100), is(equalTo(100))));
currentTime = dmxBridgeHandler.calcBuffer(currentTime, 1000);
waitForAssert(() -> assertThat(dmxBridgeHandler.getDmxChannelValue(100), is(equalTo(100))));
// step II (holds forever)
currentTime = dmxBridgeHandler.calcBuffer(currentTime, 1000);
waitForAssert(() -> assertThat(dmxBridgeHandler.getDmxChannelValue(100), is(equalTo(200))));
currentTime = dmxBridgeHandler.calcBuffer(currentTime, 2000);
waitForAssert(() -> assertThat(dmxBridgeHandler.getDmxChannelValue(100), is(equalTo(200))));
}
@Test
public void runningChaser() {
initializeTestBridge();
thingProperties.put(CONFIG_CHASER_STEPS, TEST_STEPS_REPEAT);
initialize();
long currentTime = System.currentTimeMillis();
chaserThingHandler.handleCommand(new ChannelUID(chaserThing.getUID(), CHANNEL_SWITCH), OnOffType.ON);
// step I
currentTime = dmxBridgeHandler.calcBuffer(currentTime, 1000);
waitForAssert(() -> assertThat(dmxBridgeHandler.getDmxChannelValue(100), is(equalTo(115))));
currentTime = dmxBridgeHandler.calcBuffer(currentTime, 1000);
waitForAssert(() -> assertThat(dmxBridgeHandler.getDmxChannelValue(100), is(equalTo(115))));
// step II
currentTime = dmxBridgeHandler.calcBuffer(currentTime, 1000);
waitForAssert(() -> assertThat(dmxBridgeHandler.getDmxChannelValue(100), is(equalTo(210))));
currentTime = dmxBridgeHandler.calcBuffer(currentTime, 1000);
waitForAssert(() -> assertThat(dmxBridgeHandler.getDmxChannelValue(100), is(equalTo(210))));
// step I (repeated)
currentTime = dmxBridgeHandler.calcBuffer(currentTime, 1000);
waitForAssert(() -> assertThat(dmxBridgeHandler.getDmxChannelValue(100), is(equalTo(115))));
currentTime = dmxBridgeHandler.calcBuffer(currentTime, 1000);
waitForAssert(() -> assertThat(dmxBridgeHandler.getDmxChannelValue(100), is(equalTo(115))));
}
@Test
public void runningChaserWithResume() {
initializeTestBridge();
thingProperties.put(CONFIG_CHASER_STEPS, TEST_STEPS_REPEAT);
thingProperties.put(CONFIG_CHASER_RESUME_AFTER, true);
initialize();
dmxBridgeHandler.setDmxChannelValue(100, 193);
long currentTime = System.currentTimeMillis();
currentTime = dmxBridgeHandler.calcBuffer(currentTime, 0);
waitForAssert(() -> assertThat(dmxBridgeHandler.getDmxChannelValue(100), is(equalTo(193))));
chaserThingHandler.handleCommand(new ChannelUID(chaserThing.getUID(), CHANNEL_SWITCH), OnOffType.ON);
// step I
currentTime = dmxBridgeHandler.calcBuffer(currentTime, 1000);
waitForAssert(() -> assertThat(dmxBridgeHandler.getDmxChannelValue(100), is(equalTo(115))));
currentTime = dmxBridgeHandler.calcBuffer(currentTime, 1000);
waitForAssert(() -> assertThat(dmxBridgeHandler.getDmxChannelValue(100), is(equalTo(115))));
// step II
currentTime = dmxBridgeHandler.calcBuffer(currentTime, 1000);
waitForAssert(() -> assertThat(dmxBridgeHandler.getDmxChannelValue(100), is(equalTo(210))));
currentTime = dmxBridgeHandler.calcBuffer(currentTime, 1000);
waitForAssert(() -> assertThat(dmxBridgeHandler.getDmxChannelValue(100), is(equalTo(210))));
// resume old state
currentTime = dmxBridgeHandler.calcBuffer(currentTime, 1000);
waitForAssert(() -> assertThat(dmxBridgeHandler.getDmxChannelValue(100), is(equalTo(193))));
}
private void initialize() {
chaserThing = ThingBuilder.create(THING_TYPE_CHASER, "testchaser").withLabel("Chaser Thing")
.withBridge(bridge.getUID()).withConfiguration(new Configuration(thingProperties))
.withChannel(
ChannelBuilder.create(CHANNEL_UID_SWITCH, "Switch").withType(SWITCH_CHANNEL_TYPEUID).build())
.build();
chaserThingHandler = new ChaserThingHandler(chaserThing) {
@Override
protected @Nullable Bridge getBridge() {
return bridge;
}
};
initializeHandler(chaserThingHandler);
}
private void initializeTestBridge() {
bridgeProperties = new HashMap<>();
bridge = BridgeBuilder.create(THING_TYPE_TEST_BRIDGE, "testbridge").withLabel("Test Bridge")
.withConfiguration(new Configuration(bridgeProperties)).build();
dmxBridgeHandler = new TestBridgeHandler(bridge);
bridge.setHandler(dmxBridgeHandler);
ThingHandlerCallback bridgeHandler = mock(ThingHandlerCallback.class);
doAnswer(answer -> {
((Thing) answer.getArgument(0)).setStatusInfo(answer.getArgument(1));
return null;
}).when(bridgeHandler).statusUpdated(any(), any());
dmxBridgeHandler.setCallback(bridgeHandler);
dmxBridgeHandler.initialize();
}
}

View File

@@ -0,0 +1,218 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.handler;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.number.IsCloseTo.closeTo;
import static org.junit.Assert.*;
import static org.openhab.binding.dmx.internal.DmxBindingConstants.*;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.Before;
import org.junit.Test;
import org.openhab.binding.dmx.test.AbstractDmxThingTestParent;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.binding.builder.ThingBuilder;
/**
* Tests cases for {@link org.openhab.binding.dmx.internal.handler.ColorThingHandler} in RGB mode.
*
* @author Jan N. Klug - Initial contribution
*/
public class ColorThingHandlerTest extends AbstractDmxThingTestParent {
private static final String TEST_CHANNEL_CONFIG = "100/3";
private static final int TEST_FADE_TIME = 1500;
private static final HSBType TEST_COLOR = new HSBType(new DecimalType(280), new PercentType(100),
new PercentType(100));
private Map<String, Object> thingProperties;
private Thing dimmerThing;
private ColorThingHandler dimmerThingHandler;
private final ThingUID THING_UID_DIMMER = new ThingUID(THING_TYPE_COLOR, "testdimmer");
private final ChannelUID CHANNEL_UID_COLOR = new ChannelUID(THING_UID_DIMMER, CHANNEL_COLOR);
private final ChannelUID CHANNEL_UID_BRIGHTNESS_R = new ChannelUID(THING_UID_DIMMER, CHANNEL_BRIGHTNESS_R);
private final ChannelUID CHANNEL_UID_BRIGHTNESS_G = new ChannelUID(THING_UID_DIMMER, CHANNEL_BRIGHTNESS_G);
private final ChannelUID CHANNEL_UID_BRIGHTNESS_B = new ChannelUID(THING_UID_DIMMER, CHANNEL_BRIGHTNESS_B);
@Before
public void setUp() {
super.setup();
thingProperties = new HashMap<>();
thingProperties.put(CONFIG_DMX_ID, TEST_CHANNEL_CONFIG);
thingProperties.put(CONFIG_DIMMER_FADE_TIME, TEST_FADE_TIME);
thingProperties.put(CONFIG_DIMMER_TURNONVALUE, "255,128,0");
thingProperties.put(CONFIG_DIMMER_DYNAMICTURNONVALUE, true);
dimmerThing = ThingBuilder.create(THING_TYPE_COLOR, "testdimmer").withLabel("Dimmer Thing")
.withBridge(bridge.getUID()).withConfiguration(new Configuration(thingProperties))
.withChannel(ChannelBuilder.create(CHANNEL_UID_BRIGHTNESS_R, "Brightness R")
.withType(BRIGHTNESS_CHANNEL_TYPEUID).build())
.withChannel(ChannelBuilder.create(CHANNEL_UID_BRIGHTNESS_G, "Brightness G")
.withType(BRIGHTNESS_CHANNEL_TYPEUID).build())
.withChannel(ChannelBuilder.create(CHANNEL_UID_BRIGHTNESS_B, "Brightness B")
.withType(BRIGHTNESS_CHANNEL_TYPEUID).build())
.withChannel(ChannelBuilder.create(CHANNEL_UID_COLOR, "Color").withType(COLOR_CHANNEL_TYPEUID).build())
.build();
dimmerThingHandler = new ColorThingHandler(dimmerThing) {
@Override
protected @Nullable Bridge getBridge() {
return bridge;
}
};
initializeHandler(dimmerThingHandler);
}
@Test
public void testThingStatus() {
assertThingStatus(dimmerThing);
}
@Test
public void testThingStatus_noBridge() {
// check that thing is offline if no bridge found
ColorThingHandler dimmerHandlerWithoutBridge = new ColorThingHandler(dimmerThing) {
@Override
protected @Nullable Bridge getBridge() {
return null;
}
};
assertThingStatusWithoutBridge(dimmerHandlerWithoutBridge);
}
@Test
public void testOnOffCommand() {
// on
long currentTime = System.currentTimeMillis();
dimmerThingHandler.handleCommand(CHANNEL_UID_COLOR, OnOffType.ON);
currentTime = dmxBridgeHandler.calcBuffer(currentTime, TEST_FADE_TIME);
waitForAssert(() -> {
assertChannelStateUpdate(CHANNEL_UID_COLOR, state -> assertEquals(OnOffType.ON, state.as(OnOffType.class)));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS_R, state -> assertEquals(PercentType.HUNDRED, state));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS_G,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(50.0, 0.5))));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS_B, state -> assertEquals(PercentType.ZERO, state));
});
// off
dimmerThingHandler.handleCommand(CHANNEL_UID_COLOR, OnOffType.OFF);
currentTime = dmxBridgeHandler.calcBuffer(currentTime, TEST_FADE_TIME);
waitForAssert(() -> {
assertChannelStateUpdate(CHANNEL_UID_COLOR,
state -> assertEquals(OnOffType.OFF, state.as(OnOffType.class)));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS_R, state -> assertEquals(PercentType.ZERO, state));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS_G, state -> assertEquals(PercentType.ZERO, state));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS_B, state -> assertEquals(PercentType.ZERO, state));
});
}
@Test
public void testDynamicTurnOnValue() {
long currentTime = System.currentTimeMillis();
// turn on with arbitrary value
dimmerThingHandler.handleCommand(CHANNEL_UID_COLOR, TEST_COLOR);
currentTime = dmxBridgeHandler.calcBuffer(currentTime, TEST_FADE_TIME);
waitForAssert(() -> {
assertChannelStateUpdate(CHANNEL_UID_COLOR,
state -> assertThat(((HSBType) state).getHue().doubleValue(), is(closeTo(280, 2))));
assertChannelStateUpdate(CHANNEL_UID_COLOR,
state -> assertThat(((HSBType) state).getSaturation().doubleValue(), is(closeTo(100.0, 1))));
assertChannelStateUpdate(CHANNEL_UID_COLOR,
state -> assertThat(((HSBType) state).getBrightness().doubleValue(), is(closeTo(100.0, 1))));
});
// turn off and hopefully store last value
dimmerThingHandler.handleCommand(CHANNEL_UID_COLOR, OnOffType.OFF);
currentTime = dmxBridgeHandler.calcBuffer(currentTime, TEST_FADE_TIME);
waitForAssert(() -> {
assertChannelStateUpdate(CHANNEL_UID_COLOR,
state -> assertThat(((HSBType) state).getBrightness().doubleValue(), is(closeTo(0.0, 1))));
});
// turn on and restore value
dimmerThingHandler.handleCommand(CHANNEL_UID_COLOR, OnOffType.ON);
currentTime = dmxBridgeHandler.calcBuffer(currentTime, TEST_FADE_TIME);
waitForAssert(() -> {
assertChannelStateUpdate(CHANNEL_UID_COLOR,
state -> assertThat(((HSBType) state).getHue().doubleValue(), is(closeTo(280, 2))));
assertChannelStateUpdate(CHANNEL_UID_COLOR,
state -> assertThat(((HSBType) state).getSaturation().doubleValue(), is(closeTo(100.0, 1))));
assertChannelStateUpdate(CHANNEL_UID_COLOR,
state -> assertThat(((HSBType) state).getBrightness().doubleValue(), is(closeTo(100.0, 1))));
});
}
@Test
public void testPercentTypeCommand() {
assertPercentTypeCommands(dimmerThingHandler, CHANNEL_UID_COLOR, TEST_FADE_TIME);
}
@Test
public void testColorCommand() {
// setting of color
long currentTime = System.currentTimeMillis();
dimmerThingHandler.handleCommand(CHANNEL_UID_COLOR, TEST_COLOR);
currentTime = dmxBridgeHandler.calcBuffer(currentTime, TEST_FADE_TIME);
waitForAssert(() -> {
assertChannelStateUpdate(CHANNEL_UID_COLOR,
state -> assertThat(((HSBType) state).getHue().doubleValue(), is(closeTo(280, 1))));
assertChannelStateUpdate(CHANNEL_UID_COLOR,
state -> assertThat(((HSBType) state).getSaturation().doubleValue(), is(closeTo(100.0, 0.5))));
assertChannelStateUpdate(CHANNEL_UID_COLOR,
state -> assertThat(((HSBType) state).getBrightness().doubleValue(), is(closeTo(100.0, 0.5))));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS_R,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(66.5, 0.5))));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS_G, state -> assertEquals(PercentType.ZERO, state));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS_B, state -> assertEquals(PercentType.HUNDRED, state));
});
// color dimming
dimmerThingHandler.handleCommand(CHANNEL_UID_COLOR, new PercentType(30));
currentTime = dmxBridgeHandler.calcBuffer(currentTime, TEST_FADE_TIME);
waitForAssert(() -> {
assertChannelStateUpdate(CHANNEL_UID_COLOR,
state -> assertThat(((HSBType) state).getHue().doubleValue(), is(closeTo(280, 2))));
assertChannelStateUpdate(CHANNEL_UID_COLOR,
state -> assertThat(((HSBType) state).getSaturation().doubleValue(), is(closeTo(100.0, 1))));
assertChannelStateUpdate(CHANNEL_UID_COLOR,
state -> assertThat(((HSBType) state).getBrightness().doubleValue(), is(closeTo(30.0, 1))));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS_R,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(19.2, 0.5))));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS_G, state -> assertEquals(PercentType.ZERO, state));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS_B,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(29.8, 0.5))));
});
}
}

View File

@@ -0,0 +1,155 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.handler;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.number.IsCloseTo.closeTo;
import static org.junit.Assert.*;
import static org.openhab.binding.dmx.internal.DmxBindingConstants.*;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.Before;
import org.junit.Test;
import org.openhab.binding.dmx.test.AbstractDmxThingTestParent;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.binding.builder.ThingBuilder;
/**
* Tests cases for {@link org.openhab.binding.dmx.internal.handler.DimmerThingHandler} in normal
* mode.
*
* @author Jan N. Klug - Initial contribution
*/
public class DimmerThingHandlerTest extends AbstractDmxThingTestParent {
private static final String TEST_CHANNEL_CONFIG = "100";
private static final int TEST_FADE_TIME = 1500;
private Map<String, Object> thingProperties;
private Thing dimmerThing;
private DimmerThingHandler dimmerThingHandler;
private final ThingUID THING_UID_DIMMER = new ThingUID(THING_TYPE_DIMMER, "testdimmer");
private final ChannelUID CHANNEL_UID_BRIGHTNESS = new ChannelUID(THING_UID_DIMMER, CHANNEL_BRIGHTNESS);
@Before
public void setUp() {
super.setup();
thingProperties = new HashMap<>();
thingProperties.put(CONFIG_DMX_ID, TEST_CHANNEL_CONFIG);
thingProperties.put(CONFIG_DIMMER_FADE_TIME, TEST_FADE_TIME);
thingProperties.put(CONFIG_DIMMER_DYNAMICTURNONVALUE, true);
dimmerThing = ThingBuilder
.create(THING_TYPE_DIMMER, "testdimmer").withLabel("Dimmer Thing").withBridge(bridge.getUID())
.withConfiguration(new Configuration(thingProperties)).withChannel(ChannelBuilder
.create(CHANNEL_UID_BRIGHTNESS, "Brightness").withType(BRIGHTNESS_CHANNEL_TYPEUID).build())
.build();
dimmerThingHandler = new DimmerThingHandler(dimmerThing) {
@Override
protected @Nullable Bridge getBridge() {
return bridge;
}
};
initializeHandler(dimmerThingHandler);
}
@Test
public void testThingStatus() {
assertThingStatus(dimmerThing);
}
@Test
public void testThingStatus_noBridge() {
// check that thing is offline if no bridge found
DimmerThingHandler dimmerHandlerWithoutBridge = new DimmerThingHandler(dimmerThing) {
@Override
protected @Nullable Bridge getBridge() {
return null;
}
};
assertThingStatusWithoutBridge(dimmerHandlerWithoutBridge);
}
@Test
public void testOnOffCommand() {
// on
long currentTime = System.currentTimeMillis();
dimmerThingHandler.handleCommand(CHANNEL_UID_BRIGHTNESS, OnOffType.ON);
currentTime = dmxBridgeHandler.calcBuffer(currentTime, TEST_FADE_TIME);
waitForAssert(() -> {
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS,
state -> assertEquals(OnOffType.ON, state.as(OnOffType.class)));
});
// off
dimmerThingHandler.handleCommand(CHANNEL_UID_BRIGHTNESS, OnOffType.OFF);
currentTime = dmxBridgeHandler.calcBuffer(currentTime, TEST_FADE_TIME);
waitForAssert(() -> {
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS,
state -> assertEquals(OnOffType.OFF, state.as(OnOffType.class)));
});
}
@Test
public void testDynamicTurnOnValue() {
long currentTime = System.currentTimeMillis();
int testValue = 75;
// turn on with arbitrary value
dimmerThingHandler.handleCommand(CHANNEL_UID_BRIGHTNESS, new PercentType(testValue));
currentTime = dmxBridgeHandler.calcBuffer(currentTime, TEST_FADE_TIME);
waitForAssert(() -> {
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(testValue, 1.0))));
});
// turn off and hopefully store last value
dimmerThingHandler.handleCommand(CHANNEL_UID_BRIGHTNESS, OnOffType.OFF);
currentTime = dmxBridgeHandler.calcBuffer(currentTime, TEST_FADE_TIME);
waitForAssert(() -> {
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS,
state -> assertEquals(OnOffType.OFF, state.as(OnOffType.class)));
});
// turn on and restore value
dimmerThingHandler.handleCommand(CHANNEL_UID_BRIGHTNESS, OnOffType.ON);
currentTime = dmxBridgeHandler.calcBuffer(currentTime, TEST_FADE_TIME);
waitForAssert(() -> {
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(testValue, 1.0))));
});
}
@Test
public void testPercentTypeCommand() {
assertPercentTypeCommands(dimmerThingHandler, CHANNEL_UID_BRIGHTNESS, TEST_FADE_TIME);
}
}

View File

@@ -0,0 +1,150 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.handler;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.openhab.binding.dmx.internal.DmxBindingConstants.*;
import java.util.HashMap;
import java.util.Map;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mockito;
import org.openhab.binding.dmx.internal.DmxBridgeHandler;
import org.openhab.binding.dmx.internal.multiverse.BaseDmxChannel;
import org.openhab.binding.dmx.internal.multiverse.Universe;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.test.java.JavaTest;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.thing.binding.builder.BridgeBuilder;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.binding.builder.ThingBuilder;
/**
* Tests cases for {@link DmxBridgeHandler}.
*
* @author Jan N. Klug - Initial contribution
*/
public class DmxBridgeHandlerTest extends JavaTest {
/**
* simple DmxBridgeHandlerImplementation
*
* @author Jan N. Klug
*
*/
public class DmxBridgeHandlerImpl extends DmxBridgeHandler {
public DmxBridgeHandlerImpl(Bridge dmxBridge) {
super(dmxBridge);
}
@Override
public void openConnection() {
}
@Override
protected void sendDmxData() {
}
@Override
protected void closeConnection() {
}
@Override
public void initialize() {
universe = new Universe(TEST_UNIVERSE);
super.updateConfiguration();
updateStatus(ThingStatus.ONLINE);
}
}
private static final int TEST_UNIVERSE = 1;
private static final int TEST_CHANNEL = 100;
private final ThingTypeUID THING_TYPE_TEST_BRIDGE = new ThingTypeUID(BINDING_ID, "testbridge");
private final ThingUID BRIDGE_UID_TEST = new ThingUID(THING_TYPE_TEST_BRIDGE, "testbridge");
private final ChannelUID CHANNEL_UID_MUTE = new ChannelUID(BRIDGE_UID_TEST, CHANNEL_MUTE);
Map<String, Object> bridgeProperties;
private Bridge bridge;
private DmxBridgeHandlerImpl bridgeHandler;
@Before
public void setUp() {
bridgeProperties = new HashMap<>();
bridge = BridgeBuilder.create(THING_TYPE_TEST_BRIDGE, "testbridge").withLabel("Test Bridge")
.withChannel(ChannelBuilder.create(CHANNEL_UID_MUTE, "Switch").withType(MUTE_CHANNEL_TYPEUID).build())
.withConfiguration(new Configuration(bridgeProperties)).build();
ThingHandlerCallback mockCallback = mock(ThingHandlerCallback.class);
doAnswer(answer -> {
((Thing) answer.getArgument(0)).setStatusInfo(answer.getArgument(1));
return null;
}).when(mockCallback).statusUpdated(any(), any());
bridgeHandler = Mockito.spy(new DmxBridgeHandlerImpl(bridge));
bridgeHandler.getThing().setHandler(bridgeHandler);
bridgeHandler.setCallback(mockCallback);
bridgeHandler.initialize();
}
@After
public void tearDown() {
bridgeHandler.dispose();
}
@Test
public void assertBridgeStatus() {
waitForAssert(() -> assertEquals(ThingStatus.ONLINE, bridge.getStatusInfo().getStatus()));
}
@Ignore("https://github.com/eclipse/smarthome/issues/6015#issuecomment-411313627")
@Test
public void assertSendDmxDataIsCalled() {
Mockito.verify(bridgeHandler, after(500).atLeast(9)).sendDmxData();
}
@Ignore("https://github.com/eclipse/smarthome/issues/6015")
@Test
public void assertMuteChannelMutesOutput() {
bridgeHandler.handleCommand(CHANNEL_UID_MUTE, OnOffType.ON);
Mockito.verify(bridgeHandler, after(500).atMost(1)).sendDmxData();
bridgeHandler.handleCommand(CHANNEL_UID_MUTE, OnOffType.OFF);
Mockito.verify(bridgeHandler, after(500).atLeast(9)).sendDmxData();
}
@Test
public void assertRetrievingOfChannels() {
BaseDmxChannel channel = new BaseDmxChannel(TEST_UNIVERSE, TEST_CHANNEL);
BaseDmxChannel returnedChannel = bridgeHandler.getDmxChannel(channel,
ThingBuilder.create(THING_TYPE_DIMMER, "testthing").build());
Integer channelId = returnedChannel.getChannelId();
assertThat(channelId, is(TEST_CHANNEL));
}
}

View File

@@ -0,0 +1,88 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.handler;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.openhab.binding.dmx.internal.DmxBindingConstants.*;
import java.util.HashMap;
import java.util.Map;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.test.java.JavaTest;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.thing.binding.builder.BridgeBuilder;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
/**
* Tests cases for {@link org.openhab.binding.dmx.internal.handler.Lib485BridgeHandler}.
*
* @author Jan N. Klug - Initial contribution
*/
public class Lib485BridgeHandlerTest extends JavaTest {
private static final String TEST_ADDRESS = "localhost";
private final ThingUID BRIDGE_UID_LIB485 = new ThingUID(THING_TYPE_LIB485_BRIDGE, "lib485bridge");
private final ChannelUID CHANNEL_UID_MUTE = new ChannelUID(BRIDGE_UID_LIB485, CHANNEL_MUTE);
Map<String, Object> bridgeProperties;
private Bridge bridge;
private Lib485BridgeHandler bridgeHandler;
@Before
public void setUp() {
bridgeProperties = new HashMap<>();
bridgeProperties.put(CONFIG_ADDRESS, TEST_ADDRESS);
bridge = BridgeBuilder.create(THING_TYPE_LIB485_BRIDGE, "lib485bridge").withLabel("Lib485 Bridge")
.withChannel(ChannelBuilder.create(CHANNEL_UID_MUTE, "Switch").withType(MUTE_CHANNEL_TYPEUID).build())
.withConfiguration(new Configuration(bridgeProperties)).build();
ThingHandlerCallback mockCallback = mock(ThingHandlerCallback.class);
doAnswer(answer -> {
((Thing) answer.getArgument(0)).setStatusInfo(answer.getArgument(1));
return null;
}).when(mockCallback).statusUpdated(any(), any());
bridgeHandler = new Lib485BridgeHandler(bridge) {
@Override
protected void validateConfigurationParameters(Map<String, Object> configurationParameters) {
}
};
bridgeHandler.getThing().setHandler(bridgeHandler);
bridgeHandler.setCallback(mockCallback);
bridgeHandler.initialize();
}
@After
public void tearDown() {
bridgeHandler.dispose();
}
@Test
public void assertBridgeStatus() {
waitForAssert(() -> assertEquals(ThingStatus.OFFLINE, bridge.getStatusInfo().getStatus()));
}
}

View File

@@ -0,0 +1,104 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.handler;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.openhab.binding.dmx.internal.DmxBindingConstants.*;
import java.util.HashMap;
import java.util.Map;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.test.java.JavaTest;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.thing.binding.builder.BridgeBuilder;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
/**
* Tests cases for {@link org.openhab.binding.dmx.internal.handler.SacnBridgeHandler}.
*
* @author Jan N. Klug - Initial contribution
*/
public class SacnBridgeHandlerTest extends JavaTest {
private static final String TEST_ADDRESS = "localhost";
private static final int TEST_UNIVERSE = 1;
private final ThingUID BRIDGE_UID_SACN = new ThingUID(THING_TYPE_SACN_BRIDGE, "sacnbridge");
private final ChannelUID CHANNEL_UID_MUTE = new ChannelUID(BRIDGE_UID_SACN, CHANNEL_MUTE);
Map<String, Object> bridgeProperties;
private Bridge bridge;
private SacnBridgeHandler bridgeHandler;
@Before
public void setUp() {
bridgeProperties = new HashMap<>();
bridgeProperties.put(CONFIG_ADDRESS, TEST_ADDRESS);
bridgeProperties.put(CONFIG_UNIVERSE, TEST_UNIVERSE);
bridgeProperties.put(CONFIG_SACN_MODE, "unicast");
bridge = BridgeBuilder.create(THING_TYPE_SACN_BRIDGE, "sacnbridge").withLabel("sACN Bridge")
.withChannel(ChannelBuilder.create(CHANNEL_UID_MUTE, "Switch").withType(MUTE_CHANNEL_TYPEUID).build())
.withConfiguration(new Configuration(bridgeProperties)).build();
ThingHandlerCallback mockCallback = mock(ThingHandlerCallback.class);
doAnswer(answer -> {
((Thing) answer.getArgument(0)).setStatusInfo(answer.getArgument(1));
return null;
}).when(mockCallback).statusUpdated(any(), any());
bridgeHandler = new SacnBridgeHandler(bridge) {
@Override
protected void validateConfigurationParameters(Map<String, Object> configurationParameters) {
}
};
bridgeHandler.getThing().setHandler(bridgeHandler);
bridgeHandler.setCallback(mockCallback);
bridgeHandler.initialize();
}
@After
public void tearDown() {
bridgeHandler.dispose();
}
@Test
public void assertBridgeStatus() {
waitForAssert(() -> assertEquals(ThingStatus.ONLINE, bridge.getStatusInfo().getStatus()));
}
@Test
public void renamingOfUniverses() {
waitForAssert(() -> assertThat(bridgeHandler.getUniverseId(), is(TEST_UNIVERSE)));
bridgeProperties.replace(CONFIG_UNIVERSE, 2);
bridgeHandler.handleConfigurationUpdate(bridgeProperties);
waitForAssert(() -> assertThat(bridgeHandler.getUniverseId(), is(2)));
bridgeProperties.replace(CONFIG_UNIVERSE, TEST_UNIVERSE);
bridgeHandler.handleConfigurationUpdate(bridgeProperties);
waitForAssert(() -> assertThat(bridgeHandler.getUniverseId(), is(TEST_UNIVERSE)));
}
}

View File

@@ -0,0 +1,229 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.handler;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.number.IsCloseTo.closeTo;
import static org.junit.Assert.*;
import static org.openhab.binding.dmx.internal.DmxBindingConstants.*;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.Before;
import org.junit.Test;
import org.openhab.binding.dmx.test.AbstractDmxThingTestParent;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.binding.builder.ThingBuilder;
/**
* Tests cases for {@link org.openhab.binding.dmx.internal.handler.TunableWhiteThingHandler}.
*
* @author Jan N. Klug - Initial contribution
*/
public class TunableWhiteThingHandlerTest extends AbstractDmxThingTestParent {
private static final String TEST_CHANNEL_CONFIG = "100/2";
private static final int TEST_FADE_TIME = 1500;
private Map<String, Object> thingProperties;
private Thing dimmerThing;
private TunableWhiteThingHandler dimmerThingHandler;
private final ThingUID THING_UID_DIMMER = new ThingUID(THING_TYPE_TUNABLEWHITE, "testdimmer");
private final ChannelUID CHANNEL_UID_BRIGHTNESS = new ChannelUID(THING_UID_DIMMER, CHANNEL_BRIGHTNESS);
private final ChannelUID CHANNEL_UID_BRIGHTNESS_CW = new ChannelUID(THING_UID_DIMMER, CHANNEL_BRIGHTNESS_CW);
private final ChannelUID CHANNEL_UID_BRIGHTNESS_WW = new ChannelUID(THING_UID_DIMMER, CHANNEL_BRIGHTNESS_WW);
private final ChannelUID CHANNEL_UID_COLOR_TEMP = new ChannelUID(THING_UID_DIMMER, CHANNEL_COLOR_TEMPERATURE);
@Before
public void setUp() {
super.setup();
thingProperties = new HashMap<>();
thingProperties.put(CONFIG_DMX_ID, TEST_CHANNEL_CONFIG);
thingProperties.put(CONFIG_DIMMER_FADE_TIME, TEST_FADE_TIME);
thingProperties.put(CONFIG_DIMMER_TURNONVALUE, "127,127");
thingProperties.put(CONFIG_DIMMER_DYNAMICTURNONVALUE, true);
dimmerThing = ThingBuilder.create(THING_TYPE_TUNABLEWHITE, "testdimmer").withLabel("Dimmer Thing")
.withBridge(bridge.getUID()).withConfiguration(new Configuration(thingProperties))
.withChannel(ChannelBuilder.create(CHANNEL_UID_BRIGHTNESS, "Brightness")
.withType(BRIGHTNESS_CHANNEL_TYPEUID).build())
.withChannel(ChannelBuilder.create(CHANNEL_UID_BRIGHTNESS_CW, "Brightness CW")
.withType(BRIGHTNESS_CHANNEL_TYPEUID).build())
.withChannel(ChannelBuilder.create(CHANNEL_UID_BRIGHTNESS_WW, "Brightness WW")
.withType(BRIGHTNESS_CHANNEL_TYPEUID).build())
.withChannel(ChannelBuilder.create(CHANNEL_UID_COLOR_TEMP, "Color temperature")
.withType(COLOR_TEMPERATURE_CHANNEL_TYPEUID).build())
.build();
dimmerThingHandler = new TunableWhiteThingHandler(dimmerThing) {
@Override
protected @Nullable Bridge getBridge() {
return bridge;
}
};
initializeHandler(dimmerThingHandler);
}
@Test
public void testThingStatus() {
assertThingStatus(dimmerThing);
}
@Test
public void testThingStatus_noBridge() {
// check that thing is offline if no bridge found
TunableWhiteThingHandler dimmerHandlerWithoutBridge = new TunableWhiteThingHandler(dimmerThing) {
@Override
protected @Nullable Bridge getBridge() {
return null;
}
};
assertThingStatusWithoutBridge(dimmerHandlerWithoutBridge);
}
@Test
public void testOnOffCommand() {
// on
long currentTime = System.currentTimeMillis();
dimmerThingHandler.handleCommand(CHANNEL_UID_BRIGHTNESS, OnOffType.ON);
currentTime = dmxBridgeHandler.calcBuffer(currentTime, TEST_FADE_TIME);
waitForAssert(() -> {
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS,
state -> assertEquals(OnOffType.ON, state.as(OnOffType.class)));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS_CW,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(50, 0.5))));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS_WW,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(50, 0.5))));
});
// off
dimmerThingHandler.handleCommand(CHANNEL_UID_BRIGHTNESS, OnOffType.OFF);
currentTime = dmxBridgeHandler.calcBuffer(currentTime, TEST_FADE_TIME);
waitForAssert(() -> {
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS,
state -> assertEquals(OnOffType.OFF, state.as(OnOffType.class)));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS_CW, state -> assertEquals(PercentType.ZERO, state));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS_WW, state -> assertEquals(PercentType.ZERO, state));
});
}
@Test
public void testDynamicTurnOnValue() {
long currentTime = System.currentTimeMillis();
int testValue = 75;
// turn on with arbitrary value
dimmerThingHandler.handleCommand(CHANNEL_UID_BRIGHTNESS, new PercentType(testValue));
currentTime = dmxBridgeHandler.calcBuffer(currentTime, TEST_FADE_TIME);
waitForAssert(() -> {
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(testValue, 1.0))));
});
// turn off and hopefully store last value
dimmerThingHandler.handleCommand(CHANNEL_UID_BRIGHTNESS, OnOffType.OFF);
currentTime = dmxBridgeHandler.calcBuffer(currentTime, TEST_FADE_TIME);
waitForAssert(() -> {
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS,
state -> assertEquals(OnOffType.OFF, state.as(OnOffType.class)));
});
// turn on and restore value
dimmerThingHandler.handleCommand(CHANNEL_UID_BRIGHTNESS, OnOffType.ON);
currentTime = dmxBridgeHandler.calcBuffer(currentTime, TEST_FADE_TIME);
waitForAssert(() -> {
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(testValue, 1.0))));
});
}
@Test
public void testPercentTypeCommand() {
assertPercentTypeCommands(dimmerThingHandler, CHANNEL_UID_BRIGHTNESS, TEST_FADE_TIME);
}
@Test
public void testColorTemperature() {
long currentTime = System.currentTimeMillis();
dimmerThingHandler.handleCommand(CHANNEL_UID_BRIGHTNESS, OnOffType.ON);
currentTime = dmxBridgeHandler.calcBuffer(currentTime, TEST_FADE_TIME);
waitForAssert(() -> {
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(100.0, 0.5))));
assertChannelStateUpdate(CHANNEL_UID_COLOR_TEMP,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(50.0, 0.5))));
});
// cool white
dimmerThingHandler.handleCommand(CHANNEL_UID_COLOR_TEMP, PercentType.ZERO);
currentTime = dmxBridgeHandler.calcBuffer(currentTime, TEST_FADE_TIME);
waitForAssert(() -> {
assertChannelStateUpdate(CHANNEL_UID_COLOR_TEMP,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(0.0, 0.1))));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(100.0, 0.5))));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS_CW,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(100.0, 0.5))));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS_WW,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(0.0, 0.5))));
});
// warm white
dimmerThingHandler.handleCommand(CHANNEL_UID_COLOR_TEMP, PercentType.HUNDRED);
currentTime = dmxBridgeHandler.calcBuffer(currentTime, TEST_FADE_TIME);
waitForAssert(() -> {
assertChannelStateUpdate(CHANNEL_UID_COLOR_TEMP,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(100.0, 0.1))));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(100.0, 0.5))));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS_CW,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(0.0, 0.5))));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS_WW,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(100.0, 0.5))));
});
// intermediate white
dimmerThingHandler.handleCommand(CHANNEL_UID_COLOR_TEMP, new PercentType(75));
currentTime = dmxBridgeHandler.calcBuffer(currentTime, TEST_FADE_TIME);
waitForAssert(() -> {
assertChannelStateUpdate(CHANNEL_UID_COLOR_TEMP,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(75.0, 0.1))));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(100.0, 1.0))));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS_CW,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(25.0, 0.5))));
assertChannelStateUpdate(CHANNEL_UID_BRIGHTNESS_WW,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(75.0, 0.5))));
});
}
}

View File

@@ -0,0 +1,110 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.multiverse;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.assertThat;
import java.util.List;
import org.junit.Test;
/**
* Tests cases for BaseChannel
*
* @author Jan N. Klug - Initial contribution
*/
public class BaseChannelTest {
@Test
public void creatingBaseChannelFromIntegers() {
// overrange
BaseDmxChannel channel = new BaseDmxChannel(0, 600);
assertThat(channel.getChannelId(), is(BaseDmxChannel.MAX_CHANNEL_ID));
// underrange
channel = new BaseDmxChannel(0, -1);
assertThat(channel.getChannelId(), is(BaseDmxChannel.MIN_CHANNEL_ID));
// inrange & universe
channel = new BaseDmxChannel(5, 100);
assertThat(channel.getChannelId(), is(100));
assertThat(channel.getUniverseId(), is(5));
// set universe
channel.setUniverseId(1);
assertThat(channel.getUniverseId(), is(1));
}
@Test
public void creatingBaseChannelfromBaseChannel() {
BaseDmxChannel baseChannel = new BaseDmxChannel(5, 100);
BaseDmxChannel copyChannel = new BaseDmxChannel(baseChannel);
assertThat(copyChannel.getChannelId(), is(100));
assertThat(copyChannel.getUniverseId(), is(5));
}
@Test
public void comparingChannels() {
BaseDmxChannel channel1 = new BaseDmxChannel(5, 100);
BaseDmxChannel channel2 = new BaseDmxChannel(7, 140);
assertThat(channel1.compareTo(channel2), is(-1));
assertThat(channel2.compareTo(channel1), is(1));
assertThat(channel1.compareTo(channel1), is(0));
}
@Test
public void stringConversion() {
// to string
BaseDmxChannel baseChannel = new BaseDmxChannel(5, 100);
assertThat(baseChannel.toString(), is(equalTo("5:100")));
// single channel from string with universe
String parseString = new String("2:100");
List<BaseDmxChannel> channelList = BaseDmxChannel.fromString(parseString, 0);
assertThat(channelList.size(), is(1));
assertThat(channelList.get(0).toString(), is(equalTo("2:100")));
// single channel from string without universe
parseString = new String("100");
channelList = BaseDmxChannel.fromString(parseString, 2);
assertThat(channelList.size(), is(1));
assertThat(channelList.get(0).toString(), is(equalTo("2:100")));
// two channels with channel width
parseString = new String("100/2");
channelList = BaseDmxChannel.fromString(parseString, 2);
assertThat(channelList.size(), is(2));
assertThat(channelList.get(0).toString(), is(equalTo("2:100")));
assertThat(channelList.get(1).toString(), is(equalTo("2:101")));
// to channels with comma
parseString = new String("100,102");
channelList = BaseDmxChannel.fromString(parseString, 2);
assertThat(channelList.size(), is(2));
assertThat(channelList.get(0).toString(), is(equalTo("2:100")));
assertThat(channelList.get(1).toString(), is(equalTo("2:102")));
// complex string
parseString = new String("257,100/3,426");
channelList = BaseDmxChannel.fromString(parseString, 2);
assertThat(channelList.size(), is(5));
assertThat(channelList.get(0).toString(), is(equalTo("2:257")));
assertThat(channelList.get(1).toString(), is(equalTo("2:100")));
assertThat(channelList.get(2).toString(), is(equalTo("2:101")));
assertThat(channelList.get(3).toString(), is(equalTo("2:102")));
assertThat(channelList.get(4).toString(), is(equalTo("2:426")));
}
}

View File

@@ -0,0 +1,145 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.internal.multiverse;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.openhab.binding.dmx.internal.DmxBindingConstants.ListenerType;
import org.openhab.binding.dmx.internal.action.FadeAction;
import org.openhab.binding.dmx.internal.action.ResumeAction;
import org.openhab.binding.dmx.internal.handler.DimmerThingHandler;
import org.openhab.core.thing.ChannelUID;
/**
* Tests cases for Channel
*
* @author Jan N. Klug - Initial contribution
*/
public class DmxChannelTest {
private final ChannelUID valueChannelUID = new ChannelUID("dmx:testBridge:testThing:valueChannel");
DmxChannel dmxChannel;
DimmerThingHandler dimmerThingHandler;
long currentTime;
@Before
public void setup() {
dimmerThingHandler = Mockito.mock(DimmerThingHandler.class);
dmxChannel = new DmxChannel(0, 1, 0);
dmxChannel.addListener(valueChannelUID, dimmerThingHandler, ListenerType.VALUE);
dmxChannel.setValue(0);
currentTime = System.currentTimeMillis();
}
@Test
public void checkValueSettingAndReporting() {
dmxChannel.setValue(125);
dmxChannel.getNewValue(currentTime);
assertThat(dmxChannel.getValue(), is(125));
Mockito.verify(dimmerThingHandler).updateChannelValue(valueChannelUID, 125);
}
@Test
public void checkLimitsAreObserved() {
dmxChannel.setValue(300);
assertThat(dmxChannel.getValue(), is(DmxChannel.MAX_VALUE));
dmxChannel.setValue(-1);
assertThat(dmxChannel.getValue(), is(DmxChannel.MIN_VALUE));
}
@Test
public void setAndClearAction() {
// has action
dmxChannel.setChannelAction(new FadeAction(0, 100, -1));
assertThat(dmxChannel.hasRunningActions(), is(true));
// clear action
dmxChannel.clearAction();
assertThat(dmxChannel.hasRunningActions(), is(false));
}
@Test
public void checkSingleFadeAction() {
dmxChannel.addChannelAction(new FadeAction(1000, 243, -1));
dmxChannel.getNewValue(currentTime);
assertThat(dmxChannel.hasRunningActions(), is(true));
Mockito.verify(dimmerThingHandler).updateChannelValue(valueChannelUID, 0);
dmxChannel.getNewValue(currentTime + 1000);
assertThat(dmxChannel.hasRunningActions(), is(false));
Mockito.verify(dimmerThingHandler).updateChannelValue(valueChannelUID, 243);
}
@Test
public void checkMultipleInfiniteFadeAction() {
dmxChannel.addChannelAction(new FadeAction(1000, 243, 0));
dmxChannel.addChannelAction(new FadeAction(1000, 127, 0));
dmxChannel.getNewValue(currentTime);
assertThat(dmxChannel.hasRunningActions(), is(true));
Mockito.verify(dimmerThingHandler).updateChannelValue(valueChannelUID, 0);
// check first action completes
dmxChannel.getNewValue(currentTime);
currentTime += 1000;
dmxChannel.getNewValue(currentTime);
assertThat(dmxChannel.hasRunningActions(), is(true));
Mockito.verify(dimmerThingHandler).updateChannelValue(valueChannelUID, 243);
// check second action completes
dmxChannel.getNewValue(currentTime);
currentTime += 1000;
dmxChannel.getNewValue(currentTime);
assertThat(dmxChannel.hasRunningActions(), is(true));
Mockito.verify(dimmerThingHandler).updateChannelValue(valueChannelUID, 127);
// check first action completes again
currentTime += 1000;
dmxChannel.getNewValue(currentTime);
assertThat(dmxChannel.hasRunningActions(), is(true));
Mockito.verify(dimmerThingHandler).updateChannelValue(valueChannelUID, 243);
}
@Test
public void checkFadeActionWithResume() {
dmxChannel.setValue(127);
dmxChannel.suspendAction();
dmxChannel.addChannelAction(new FadeAction(1000, 243, 0));
dmxChannel.addChannelAction(new ResumeAction());
dmxChannel.getNewValue(currentTime);
assertThat(dmxChannel.hasRunningActions(), is(true));
Mockito.verify(dimmerThingHandler).updateChannelValue(valueChannelUID, 127);
// check action completes
dmxChannel.getNewValue(currentTime);
currentTime += 1000;
dmxChannel.getNewValue(currentTime);
assertThat(dmxChannel.hasRunningActions(), is(true));
Mockito.verify(dimmerThingHandler).updateChannelValue(valueChannelUID, 243);
// check state is restored
dmxChannel.getNewValue(currentTime);
assertThat(dmxChannel.hasRunningActions(), is(false));
Mockito.verify(dimmerThingHandler).updateChannelValue(valueChannelUID, 127);
}
}

View File

@@ -0,0 +1,145 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.test;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.number.IsCloseTo.closeTo;
import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.openhab.binding.dmx.test.TestBridgeHandler.THING_TYPE_TEST_BRIDGE;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.test.java.JavaTest;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.thing.binding.builder.BridgeBuilder;
import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder;
import org.openhab.core.types.State;
/**
* Common utilities for DMX thing handler tests.
*
* @author Simon Kaufmann - initial contribution and API
*
*/
public class AbstractDmxThingTestParent extends JavaTest {
private Map<String, Object> bridgeProperties;
protected Bridge bridge;
protected TestBridgeHandler dmxBridgeHandler;
protected ThingHandlerCallback mockCallback;
protected void setup() {
initializeBridge();
mockCallback = mock(ThingHandlerCallback.class);
doAnswer(answer -> {
((Thing) answer.getArgument(0)).setStatusInfo(answer.getArgument(1));
return null;
}).when(mockCallback).statusUpdated(any(), any());
}
private void initializeBridge() {
bridgeProperties = new HashMap<>();
bridge = BridgeBuilder.create(THING_TYPE_TEST_BRIDGE, "testbridge").withLabel("Test Bridge")
.withConfiguration(new Configuration(bridgeProperties)).build();
dmxBridgeHandler = new TestBridgeHandler(bridge);
bridge.setHandler(dmxBridgeHandler);
ThingHandlerCallback bridgeHandler = mock(ThingHandlerCallback.class);
doAnswer(answer -> {
((Thing) answer.getArgument(0)).setStatusInfo(answer.getArgument(1));
return null;
}).when(bridgeHandler).statusUpdated(any(), any());
dmxBridgeHandler.setCallback(bridgeHandler);
dmxBridgeHandler.initialize();
}
protected void initializeHandler(ThingHandler handler) {
handler.getThing().setHandler(handler);
handler.setCallback(mockCallback);
handler.initialize();
}
protected void assertThingStatus(Thing thing) {
// check that thing turns online if properly configured
waitForAssert(() -> assertEquals(ThingStatus.ONLINE, thing.getStatusInfo().getStatus()));
// check that thing properly follows bridge status
ThingHandler handler = thing.getHandler();
assertNotNull(handler);
handler.bridgeStatusChanged(ThingStatusInfoBuilder.create(ThingStatus.OFFLINE).build());
waitForAssert(() -> assertEquals(ThingStatus.OFFLINE, thing.getStatusInfo().getStatus()));
handler.bridgeStatusChanged(ThingStatusInfoBuilder.create(ThingStatus.ONLINE).build());
waitForAssert(() -> assertEquals(ThingStatus.ONLINE, thing.getStatusInfo().getStatus()));
}
protected void assertThingStatusWithoutBridge(ThingHandler handler) {
handler.setCallback(mockCallback);
handler.initialize();
waitForAssert(() -> {
assertEquals(ThingStatus.OFFLINE, handler.getThing().getStatus());
assertEquals(ThingStatusDetail.CONFIGURATION_ERROR, handler.getThing().getStatusInfo().getStatusDetail());
});
}
public void assertPercentTypeCommands(ThingHandler handler, ChannelUID channelUID, int fadeTime) {
long currentTime = System.currentTimeMillis();
// set 30%
handler.handleCommand(channelUID, new PercentType(30));
currentTime = dmxBridgeHandler.calcBuffer(currentTime, fadeTime);
waitForAssert(() -> {
assertChannelStateUpdate(channelUID,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(30.0, 1.0))));
});
// set 0%
handler.handleCommand(channelUID, PercentType.ZERO);
currentTime = dmxBridgeHandler.calcBuffer(currentTime, fadeTime);
waitForAssert(() -> {
assertChannelStateUpdate(channelUID, state -> assertEquals(PercentType.ZERO, state));
});
// set 100%
handler.handleCommand(channelUID, PercentType.HUNDRED);
currentTime = dmxBridgeHandler.calcBuffer(currentTime, fadeTime);
waitForAssert(() -> {
assertChannelStateUpdate(channelUID,
state -> assertThat(((PercentType) state).doubleValue(), is(closeTo(100.0, 0.5))));
});
}
protected void assertChannelStateUpdate(ChannelUID channelUID, Consumer<State> stateAssertion) {
ArgumentCaptor<State> captor = ArgumentCaptor.forClass(State.class);
verify(mockCallback, atLeastOnce()).stateUpdated(ArgumentMatchers.eq(channelUID), captor.capture());
State value = captor.getValue();
stateAssertion.accept(value);
}
}

View File

@@ -0,0 +1,115 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dmx.test;
import static org.openhab.binding.dmx.internal.DmxBindingConstants.BINDING_ID;
import java.util.Collections;
import java.util.Set;
import org.openhab.binding.dmx.internal.DmxBridgeHandler;
import org.openhab.binding.dmx.internal.multiverse.BaseDmxChannel;
import org.openhab.binding.dmx.internal.multiverse.Universe;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link TestBridgeHandler} is only for testing
*
* @author Jan N. Klug - Initial contribution
*/
public class TestBridgeHandler extends DmxBridgeHandler {
public static final ThingTypeUID THING_TYPE_TEST_BRIDGE = new ThingTypeUID(BINDING_ID, "test-bridge");
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_TEST_BRIDGE);
public static final int MIN_UNIVERSE_ID = 0;
public static final int MAX_UNIVERSE_ID = 0;
private final Logger logger = LoggerFactory.getLogger(TestBridgeHandler.class);
public TestBridgeHandler(Bridge testBridge) {
super(testBridge);
}
@Override
protected void openConnection() {
}
@Override
protected void closeConnection() {
}
@Override
protected void sendDmxData() {
}
@Override
protected void updateConfiguration() {
universe = new Universe(MIN_UNIVERSE_ID);
universe.setRefreshTime(0);
super.updateConfiguration();
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
logger.debug("updated configuration for Test bridge {}", this.thing.getUID());
}
@Override
public void initialize() {
logger.debug("initializing Test bridge {}", this.thing.getUID());
updateConfiguration();
}
/**
* calc buffer for timestamp after timespam
*
* @param time UNIX timestamp of calculation time
* @return new timestamp
*/
public long calcBuffer(long time, long timespan) {
logger.debug("calculating buffer for {}", time);
universe.calculateBuffer(time);
logger.debug("calculating buffer for {}", time + timespan);
universe.calculateBuffer(time + timespan);
return time + timespan;
}
/**
* get a single value from the dmxBuffer
*
* @param dmxChannel channel number (1-512)
* @return channel value
*/
public int getDmxChannelValue(int dmxChannel) {
return universe.getBuffer()[dmxChannel - 1] & 0xFF;
}
public void setDmxChannelValue(int dmxChannel, int value) {
this.getDmxChannel(new BaseDmxChannel(MIN_UNIVERSE_ID, dmxChannel), null).setValue(value);
}
/**
* update bridge status manually
*
* @param status a ThingStatus to set the bridge to
*/
public void updateBridgeStatus(ThingStatus status) {
updateStatus(status);
}
}