[dali] Implement DT8 (single-channel RGB & color temperature) device type (#12955)

* DALI: Implement DT8 (single-channel RGB & color temperature) device type, fix device and group addressing
* dali: Store BridgeHandler in a local variable instead of retrieving it over and over again.
* dali: Follow logging guidelines.

Signed-off-by: Sebastian Philipp <github-ebqurd@s3lph.me>
This commit is contained in:
Sebastian P 2022-07-01 08:11:00 +02:00 committed by GitHub
parent 3fe5f3f267
commit 4e2d87da5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 363 additions and 17 deletions

View File

@ -13,6 +13,8 @@ Currently, these things are supported:
- device (single device/ballast on the DALI bus)
- group (group of DALI devices)
- rgb (virtual device consisting of three directly addressed devices that represent r/g/b (LED) color channels)
- device-dt8 (single device/ballast supporting DT8 (single-channel RGB & color temperature control))
- group-dt8 (group of DALI devices supporting DT8)
This binding was tested on a DALI 1 bus with daliserver 0.2.
@ -37,9 +39,10 @@ Automatic device discovery is not yet implemented.
### group
| Parameter | Parameter ID | Required/Optional | description |
|-------------|--------------|-------------------|----------------------------------------|
| Group ID | targetId | Required | Address of group in the DALI bus |
| Parameter | Parameter ID | Required/Optional | description |
|----------------|--------------------|-------------------|----------------------------------------------------------------------------------------------|
| Group ID | targetId | Required | Address of group in the DALI bus |
| Read Device ID | readDeviceTargetId | Optional | If reading values from this group fails, you can choose to read from a single device instead |
### rgb
@ -49,6 +52,19 @@ Automatic device discovery is not yet implemented.
| G Device ID | targetIdG | Required | Address of device in the DALI bus |
| B Device ID | targetIdB | Required | Address of device in the DALI bus |
### device-dt8
| Parameter | Parameter ID | Required/Optional | description |
|-------------|--------------|-------------------|----------------------------------------|
| Device ID | targetId | Required | Address of device in the DALI bus |
### group-dt8
| Parameter | Parameter ID | Required/Optional | description |
|----------------|--------------------|-------------------|----------------------------------------------------------------------------------------------|
| Group ID | targetId | Required | Address of group in the DALI bus |
| Read Device ID | readDeviceTargetId | Optional | If reading values from this group fails, you can choose to read from a single device instead |
## Full Example
.things file
@ -57,7 +73,7 @@ Automatic device discovery is not yet implemented.
Bridge dali:daliserver:237dbae7 "Daliserver" [ host="localhost", port=55825] {
Thing rgb 87bf0403-a45d-4037-b874-28f4ece30004 "RGB Lights" [ targetIdR=0, targetIdG=1, targetIdB=2 ]
Thing device 995e16ca-07c4-4111-9cda-504cb5120f82 "Warm White" [ targetId=3 ]
Thing group 31da8dac-8e09-455a-bc7a-6ed70f740001 "Living Room Lights" [ targetId=0 ]
Thing group 31da8dac-8e09-455a-bc7a-6ed70f740001 "Living Room Lights" [ targetId=0, readDeviceTargetId=3 ]
}
```

View File

@ -35,15 +35,18 @@ public class DaliBindingConstants {
public static final ThingTypeUID THING_TYPE_DEVICE = new ThingTypeUID(BINDING_ID, "device");
public static final ThingTypeUID THING_TYPE_GROUP = new ThingTypeUID(BINDING_ID, "group");
public static final ThingTypeUID THING_TYPE_RGB = new ThingTypeUID(BINDING_ID, "rgb");
public static final Set<ThingTypeUID> SUPPORTED_DEVICE_THING_TYPES_UIDS = new HashSet<>(
Arrays.asList(THING_TYPE_DEVICE, THING_TYPE_GROUP, THING_TYPE_RGB));
public static final ThingTypeUID THING_TYPE_DEVICE_DT8 = new ThingTypeUID(BINDING_ID, "device-dt8");
public static final ThingTypeUID THING_TYPE_GROUP_DT8 = new ThingTypeUID(BINDING_ID, "group-dt8");
public static final Set<ThingTypeUID> SUPPORTED_DEVICE_THING_TYPES_UIDS = new HashSet<>(Arrays
.asList(THING_TYPE_DEVICE, THING_TYPE_GROUP, THING_TYPE_RGB, THING_TYPE_DEVICE_DT8, THING_TYPE_GROUP_DT8));
public static final String CHANNEL_DIM_AT_FADE_RATE = "dimAtFadeRate";
public static final String CHANNEL_DIM_IMMEDIATELY = "dimImmediately";
public static final String CHANNEL_COLOR = "color";
public static final String CHANNEL_COLOR_TEMPERATURE = "color-temperature-abs";
public static final String TARGET_ID = "targetId";
public static final String READ_DEVICE_TARGET_ID = "readDeviceTargetId";
public static final String TARGET_ID_R = "targetIdR";
public static final String TARGET_ID_G = "targetIdG";
public static final String TARGET_ID_B = "targetIdB";

View File

@ -21,6 +21,7 @@ import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.dali.internal.handler.DaliDeviceHandler;
import org.openhab.binding.dali.internal.handler.DaliDt8DeviceHandler;
import org.openhab.binding.dali.internal.handler.DaliRgbHandler;
import org.openhab.binding.dali.internal.handler.DaliserverBridgeHandler;
import org.openhab.core.thing.Bridge;
@ -64,6 +65,9 @@ public class DaliHandlerFactory extends BaseThingHandlerFactory {
if (THING_TYPE_RGB.equals(thingTypeUID)) {
return new DaliRgbHandler(thing);
}
if (THING_TYPE_DEVICE_DT8.equals(thingTypeUID) || THING_TYPE_GROUP_DT8.equals(thingTypeUID)) {
return new DaliDt8DeviceHandler(thing);
}
return null;
}

View File

@ -22,6 +22,7 @@ import org.openhab.binding.dali.internal.protocol.DaliAddress;
import org.openhab.binding.dali.internal.protocol.DaliDAPCCommand;
import org.openhab.binding.dali.internal.protocol.DaliResponse;
import org.openhab.binding.dali.internal.protocol.DaliStandardCommand;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
@ -45,7 +46,8 @@ import org.slf4j.LoggerFactory;
@NonNullByDefault
public class DaliDeviceHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(DaliDeviceHandler.class);
private @Nullable Integer targetId;
protected @Nullable Integer targetId;
protected @Nullable Integer readDeviceTargetId;
public DaliDeviceHandler(Thing thing) {
super(thing);
@ -61,7 +63,16 @@ public class DaliDeviceHandler extends BaseThingHandler {
updateStatus(ThingStatus.ONLINE);
}
targetId = ((BigDecimal) this.thing.getConfiguration().get(TARGET_ID)).intValueExact();
final Configuration conf = this.thing.getConfiguration();
targetId = ((BigDecimal) conf.get(TARGET_ID)).intValueExact();
// Reading from group addresses does not work generally, so if a fallback device id is
// defined, use that instead when reading the current state
if (conf.get(READ_DEVICE_TARGET_ID) != null) {
readDeviceTargetId = ((BigDecimal) this.thing.getConfiguration().get(READ_DEVICE_TARGET_ID))
.intValueExact();
} else {
readDeviceTargetId = null;
}
}
@Override
@ -70,9 +81,11 @@ public class DaliDeviceHandler extends BaseThingHandler {
if (CHANNEL_DIM_AT_FADE_RATE.equals(channelUID.getId())
|| CHANNEL_DIM_IMMEDIATELY.equals(channelUID.getId())) {
DaliAddress address;
if (THING_TYPE_DEVICE.equals(this.thing.getThingTypeUID())) {
if (THING_TYPE_DEVICE.equals(this.thing.getThingTypeUID())
|| THING_TYPE_DEVICE_DT8.equals(this.thing.getThingTypeUID())) {
address = DaliAddress.createShortAddress(targetId);
} else if (THING_TYPE_GROUP.equals(this.thing.getThingTypeUID())) {
} else if (THING_TYPE_GROUP.equals(this.thing.getThingTypeUID())
|| THING_TYPE_GROUP_DT8.equals(this.thing.getThingTypeUID())) {
address = DaliAddress.createGroupAddress(targetId);
} else {
throw new DaliException("unknown device type");
@ -110,8 +123,12 @@ public class DaliDeviceHandler extends BaseThingHandler {
}
if (queryDeviceState) {
DaliAddress readAddress = address;
if (readDeviceTargetId != null) {
readAddress = DaliAddress.createShortAddress(readDeviceTargetId);
}
getBridgeHandler()
.sendCommandWithResponse(DaliStandardCommand.createQueryActualLevelCommand(address),
.sendCommandWithResponse(DaliStandardCommand.createQueryActualLevelCommand(readAddress),
DaliResponse.NumericMask.class)
.thenAccept(response -> {
if (response != null && !response.mask) {

View File

@ -0,0 +1,187 @@
/**
* Copyright (c) 2010-2022 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.dali.internal.handler;
import static org.openhab.binding.dali.internal.DaliBindingConstants.CHANNEL_COLOR;
import static org.openhab.binding.dali.internal.DaliBindingConstants.CHANNEL_COLOR_TEMPERATURE;
import static org.openhab.binding.dali.internal.DaliBindingConstants.THING_TYPE_DEVICE_DT8;
import static org.openhab.binding.dali.internal.DaliBindingConstants.THING_TYPE_GROUP_DT8;
import java.util.concurrent.CompletableFuture;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.dali.internal.protocol.DaliAddress;
import org.openhab.binding.dali.internal.protocol.DaliResponse;
import org.openhab.binding.dali.internal.protocol.DaliResponse.NumericMask;
import org.openhab.binding.dali.internal.protocol.DaliStandardCommand;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.PercentType;
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.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link DaliDeviceHandler} handles commands for things of type Device and Group.
*
* @author Robert Schmid - Initial contribution
*/
@NonNullByDefault
public class DaliDt8DeviceHandler extends DaliDeviceHandler {
private final Logger logger = LoggerFactory.getLogger(DaliDt8DeviceHandler.class);
public DaliDt8DeviceHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
try {
final DaliserverBridgeHandler daliHandler = getBridgeHandler();
if (CHANNEL_COLOR_TEMPERATURE.equals(channelUID.getId())) {
DaliAddress address;
if (THING_TYPE_DEVICE_DT8.equals(this.thing.getThingTypeUID())) {
address = DaliAddress.createShortAddress(targetId);
} else if (THING_TYPE_GROUP_DT8.equals(this.thing.getThingTypeUID())) {
address = DaliAddress.createGroupAddress(targetId);
} else {
throw new DaliException("unknown device type");
}
if (command instanceof DecimalType) {
// Color temperature in DALI is represented in mirek ("reciprocal megakelvin")
// It is one million times the reciprocal of the color temperature (in Kelvin)
final int mirek = (int) (1E6f
/ (Math.min(Math.max(((DecimalType) command).intValue(), 1000), 20000)));
final byte mirekLsb = (byte) (mirek & 0xff);
final byte mirekMsb = (byte) ((mirek >> 8) & 0xff);
// Write mirek value to the DTR0+DTR1 registers
daliHandler.sendCommand(DaliStandardCommand.createSetDTR0Command(mirekLsb));
daliHandler.sendCommand(DaliStandardCommand.createSetDTR1Command(mirekMsb));
// Indicate that the follwing command is a DT8 (WW/CW and single-channel RGB) command
daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
// Set the color temperature to the value in DTR0+DTR1
daliHandler.sendCommand(DaliStandardCommand.createSetColorTemperatureCommand(address));
// Finish the command sequence
daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
daliHandler.sendCommand(DaliStandardCommand.createActivateCommand(address));
}
DaliAddress readAddress = address;
if (readDeviceTargetId != null) {
readAddress = DaliAddress.createShortAddress(readDeviceTargetId);
}
// Write argument 0x02 (query color temperature) to DTR0 and set DT8
daliHandler.sendCommand(DaliStandardCommand.createSetDTR0Command(2));
daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
// Mirek MSB is returned as result
CompletableFuture<@Nullable NumericMask> responseMsb = daliHandler.sendCommandWithResponse(
DaliStandardCommand.createQueryColorValueCommand(readAddress), DaliResponse.NumericMask.class);
daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
// Mirek LSB is written to DTR0
CompletableFuture<@Nullable NumericMask> responseLsb = daliHandler.sendCommandWithResponse(
DaliStandardCommand.createQueryContentDTR0Command(readAddress), DaliResponse.NumericMask.class);
CompletableFuture.allOf(responseMsb, responseLsb).thenAccept(x -> {
@Nullable
NumericMask msb = responseMsb.join(), lsb = responseLsb.join();
if (msb != null && !msb.mask && lsb != null && !lsb.mask) {
final int msbValue = msb.value != null ? msb.value : 0;
final int lsbValue = lsb.value != null ? lsb.value : 0;
final int mirek = ((msbValue & 0xff) << 8) | (lsbValue & 0xff);
final int kelvin = (int) (1E6f / mirek);
updateState(channelUID, new DecimalType(kelvin));
}
}).exceptionally(e -> {
logger.warn("Error querying device status: {}", e.getMessage());
return null;
});
} else if (CHANNEL_COLOR.equals(channelUID.getId())) {
DaliAddress address;
if (THING_TYPE_DEVICE_DT8.equals(this.thing.getThingTypeUID())) {
address = DaliAddress.createShortAddress(targetId);
} else if (THING_TYPE_GROUP_DT8.equals(this.thing.getThingTypeUID())) {
address = DaliAddress.createGroupAddress(targetId);
} else {
throw new DaliException("unknown device type");
}
if (command instanceof HSBType) {
PercentType[] rgb = ((HSBType) command).toRGB();
final int r = (int) (254 * (rgb[0].floatValue() / 100));
final int g = (int) (254 * (rgb[1].floatValue() / 100));
final int b = (int) (254 * (rgb[2].floatValue() / 100));
logger.trace("RGB: {} {} {}", r, g, b);
// Write RGB values to the DTR0+DTR1+DTR2 registers
daliHandler.sendCommand(DaliStandardCommand.createSetDTR0Command(r));
daliHandler.sendCommand(DaliStandardCommand.createSetDTR1Command(g));
daliHandler.sendCommand(DaliStandardCommand.createSetDTR2Command(b));
// Indicate that the following command is a DT8 (WW/CW and single-channel RGB) command
daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
// Set the color to the values in DTR0+DTR1+DTR2
daliHandler.sendCommand(DaliStandardCommand.createSetRgbDimlevelCommand(address));
// Finish the command sequence
daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
daliHandler.sendCommand(DaliStandardCommand.createActivateCommand(address));
}
DaliAddress readAddress = address;
if (readDeviceTargetId != null) {
readAddress = DaliAddress.createShortAddress(readDeviceTargetId);
}
// Write argument 0xE9 (query red dimlevel) to DTR0 and set DT8
daliHandler.sendCommand(DaliStandardCommand.createSetDTR0Command(0xe9));
daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
// Red component is returned as result
CompletableFuture<@Nullable NumericMask> responseRed = daliHandler.sendCommandWithResponse(
DaliStandardCommand.createQueryColorValueCommand(readAddress), DaliResponse.NumericMask.class);
// Write argument 0xEA (query green dimlevel) to DTR0 and set DT8
daliHandler.sendCommand(DaliStandardCommand.createSetDTR0Command(0xea));
daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
// Green component is returned as result
CompletableFuture<@Nullable NumericMask> responseGreen = daliHandler.sendCommandWithResponse(
DaliStandardCommand.createQueryColorValueCommand(readAddress), DaliResponse.NumericMask.class);
// Write argument 0xEB (query blue dimlevel) to DTR0 and set DT8
daliHandler.sendCommand(DaliStandardCommand.createSetDTR0Command(0xeb));
daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
// Blue component is returned as result
CompletableFuture<@Nullable NumericMask> responseBlue = daliHandler.sendCommandWithResponse(
DaliStandardCommand.createQueryColorValueCommand(readAddress), DaliResponse.NumericMask.class);
CompletableFuture.allOf(responseRed, responseGreen, responseBlue).thenAccept(x -> {
@Nullable
NumericMask r = responseRed.join(), g = responseGreen.join(), b = responseBlue.join();
if (r != null && !r.mask && g != null && !g.mask && b != null && !b.mask) {
final int rValue = r.value != null ? r.value : 0;
final int gValue = g.value != null ? g.value : 0;
final int bValue = b.value != null ? b.value : 0;
updateState(channelUID, HSBType.fromRGB(rValue, gValue, bValue));
}
}).exceptionally(e -> {
logger.warn("Error querying device status: {}", e.getMessage());
return null;
});
} else {
super.handleCommand(channelUID, command);
}
} catch (DaliException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
}

View File

@ -142,6 +142,7 @@ public class DaliserverBridgeHandler extends BaseBridgeHandler {
} catch (Exception e) {
logger.warn("Unexpected exception while sending command to daliserver: {} Message: {}", frame,
e.getMessage());
logger.trace("Stacktrace", e);
future.completeExceptionally(e);
}
});
@ -161,7 +162,7 @@ public class DaliserverBridgeHandler extends BaseBridgeHandler {
}
byte status = response[1], rval = response[2];
if (status == 0) {
result.parse(null);
// No return value to process.
} else if (status == 1) {
result.parse(new DaliBackwardFrame(rval));
} else if (status == 255) {

View File

@ -36,10 +36,27 @@ public abstract class DaliAddress {
protected <T extends DaliFrame> T addToFrame(T frame) throws DaliException {
if (frame.length() == 16) {
frame.data &= ~(1 << 15); // unset bit 15
frame.data |= ((address & 0b11111) << 9);
frame.data |= ((address & 0b111111) << 9);
} else if (frame.length() == 24) {
frame.data &= ~(1 << 23); // unset bit 23
frame.data |= ((address & 0b11111) << 17);
frame.data |= ((address & 0b111111) << 17);
} else {
throw new DaliException("Unsupported frame size");
}
return frame;
}
};
}
static DaliAddress createRawAddress(int address) {
return new DaliAddress() {
@Override
protected <T extends DaliFrame> T addToFrame(T frame) throws DaliException {
// Keep all bits of the raw address
if (frame.length() == 16) {
frame.data |= ((address & 0xff) << 8);
} else if (frame.length() == 24) {
frame.data |= ((address & 0xff) << 16);
} else {
throw new DaliException("Unsupported frame size");
}
@ -91,9 +108,9 @@ public abstract class DaliAddress {
if (address > 15) {
throw new DaliException("Groups 16..31 are not supported in 16-bit forward frames");
}
frame.data |= ((0x4 << 3) & (address & 0b111)) << 9;
frame.data |= (0x80 | ((address & 0b1111) << 1)) << 8;
} else if (frame.length() == 24) {
frame.data |= ((0x2 << 4) & (address & 0b1111)) << 17;
frame.data |= (0x80 | ((address & 0b11111) << 1)) << 16;
} else {
throw new DaliException("Unsupported frame size");
}

View File

@ -81,7 +81,47 @@ public class DaliStandardCommand extends DaliGearCommandBase {
return new DaliStandardCommand(target, 0x90, 0, false);
}
public static DaliStandardCommand createQueryContentDTR0Command(DaliAddress target) throws DaliException {
return new DaliStandardCommand(target, 0x98, 0, false);
}
public static DaliStandardCommand createQueryActualLevelCommand(DaliAddress target) throws DaliException {
return new DaliStandardCommand(target, 0xa0, 0, false);
}
public static DaliStandardCommand createActivateCommand(DaliAddress target) throws DaliException {
return new DaliStandardCommand(target, 0xe2, 0, false);
}
public static DaliStandardCommand createQueryColorValueCommand(DaliAddress target) throws DaliException {
return new DaliStandardCommand(target, 0xfa, 0, false);
}
// Global commands sent to special addresses
public static DaliStandardCommand createSetDTR0Command(int value) throws DaliException {
return new DaliStandardCommand(DaliAddress.createRawAddress(0xa3), value, 0, false);
}
public static DaliStandardCommand createSetDTR1Command(int value) throws DaliException {
return new DaliStandardCommand(DaliAddress.createRawAddress(0xc3), value, 0, false);
}
public static DaliStandardCommand createSetDTR2Command(int value) throws DaliException {
return new DaliStandardCommand(DaliAddress.createRawAddress(0xc5), value, 0, false);
}
public static DaliStandardCommand createSetDeviceTypeCommand(int value) throws DaliException {
return new DaliStandardCommand(DaliAddress.createRawAddress(0xc1), value, 0, false);
}
// DT8 (color temperature and single-channel RGB) commands
public static DaliStandardCommand createSetRgbDimlevelCommand(DaliAddress target) throws DaliException {
return new DaliStandardCommand(target, 0xeb, 0, false);
}
public static DaliStandardCommand createSetColorTemperatureCommand(DaliAddress target) throws DaliException {
return new DaliStandardCommand(target, 0xe7, 0, false);
}
}

View File

@ -13,6 +13,10 @@ thing-type.dali.group.label = DALI Group
thing-type.dali.group.description = Controls a group of devices/ballasts
thing-type.dali.rgb.label = DALI RGB Device
thing-type.dali.rgb.description = Controls three DALI devices representing R,G,B lighting channels
thing-type.dali.device-dt8.label = DALI DT8 Device
thing-type.dali.device-dt8.description = Controls a single DT8 device/ballast
thing-type.dali.group-dt8.label = DALI DT8 Group
thing-type.dali.group-dt8.description = Controls a group of DT8 device/ballast
# thing types config
@ -24,6 +28,8 @@ thing-type.config.dali.device.targetId.label = Device ID
thing-type.config.dali.device.targetId.description = Address of the device in the DALI bus
thing-type.config.dali.group.targetId.label = Group ID
thing-type.config.dali.group.targetId.description = Address of the group in the DALI bus
thing-type.config.dali.group.readDeviceTargetId.label = Read Device ID
thing-type.config.dali.group.readDeviceTargetId.description = If reading values from this group fails, you can choose to read from a single device instead.
thing-type.config.dali.rgb.targetIdB.label = B Device ID
thing-type.config.dali.rgb.targetIdB.description = Address of the device in the DALI bus
thing-type.config.dali.rgb.targetIdG.label = G Device ID

View File

@ -48,6 +48,12 @@
<label>Group ID</label>
<description>Address of the group in the DALI bus</description>
</parameter>
<parameter name="readDeviceTargetId" type="integer" required="false" min="0" max="63">
<label>Read Device ID</label>
<description>
If reading values from this group fails, you can choose to read from a single device instead.
</description>
</parameter>
</config-description>
</thing-type>
@ -78,4 +84,53 @@
</config-description>
</thing-type>
<!-- Single DT8 Device Type -->
<thing-type id="device-dt8">
<supported-bridge-type-refs>
<bridge-type-ref id="daliserver"/>
</supported-bridge-type-refs>
<label>DALI DT8 Device</label>
<description>Controls a single DT8 device/ballast</description>
<channels>
<channel id="dimAtFadeRate" typeId="system.brightness"/>
<channel id="dimImmediately" typeId="system.brightness"/>
<channel id="color" typeId="system.color"/>
<channel id="color-temperature-abs" typeId="system.color-temperature-abs"/>
</channels>
<config-description>
<parameter name="targetId" type="integer" required="true" min="0" max="63">
<label>Device ID</label>
<description>Address of the device in the DALI bus</description>
</parameter>
</config-description>
</thing-type>
<!-- Group DT8 Device Type -->
<thing-type id="group-dt8">
<supported-bridge-type-refs>
<bridge-type-ref id="daliserver"/>
</supported-bridge-type-refs>
<label>DALI DT8 Group</label>
<description>Controls a DT8 group of devices/ballasts</description>
<channels>
<channel id="dimAtFadeRate" typeId="system.brightness"/>
<channel id="dimImmediately" typeId="system.brightness"/>
<channel id="color" typeId="system.color"/>
<channel id="color-temperature-abs" typeId="system.color-temperature-abs"/>
</channels>
<config-description>
<parameter name="targetId" type="integer" required="true" min="0" max="31">
<label>Group ID</label>
<description>Address of the group in the DALI bus</description>
</parameter>
<parameter name="readDeviceTargetId" type="integer" required="false" min="0" max="63">
<label>Read Device ID</label>
<description>
If reading values from this group fails, you can choose to read from a single device instead.
</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>