* 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>
- 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.
### 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
| 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
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 ]
}
```
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";
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;
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;
}
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;
@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);
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
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");
}
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) {
--- /dev/null
+/**
+ * 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());
+ }
+ }
+}
} catch (Exception e) {
logger.warn("Unexpected exception while sending command to daliserver: {} Message: {}", frame,
e.getMessage());
+ logger.trace("Stacktrace", e);
future.completeExceptionally(e);
}
});
}
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) {
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");
}
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");
}
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);
+ }
}
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
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
<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>
</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>