2 * Copyright (c) 2010-2022 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.dali.internal.handler;
15 import static org.openhab.binding.dali.internal.DaliBindingConstants.CHANNEL_COLOR;
16 import static org.openhab.binding.dali.internal.DaliBindingConstants.CHANNEL_COLOR_TEMPERATURE;
17 import static org.openhab.binding.dali.internal.DaliBindingConstants.THING_TYPE_DEVICE_DT8;
18 import static org.openhab.binding.dali.internal.DaliBindingConstants.THING_TYPE_GROUP_DT8;
20 import java.util.concurrent.CompletableFuture;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.dali.internal.protocol.DaliAddress;
25 import org.openhab.binding.dali.internal.protocol.DaliResponse;
26 import org.openhab.binding.dali.internal.protocol.DaliResponse.NumericMask;
27 import org.openhab.binding.dali.internal.protocol.DaliStandardCommand;
28 import org.openhab.core.library.types.DecimalType;
29 import org.openhab.core.library.types.HSBType;
30 import org.openhab.core.library.types.PercentType;
31 import org.openhab.core.thing.ChannelUID;
32 import org.openhab.core.thing.Thing;
33 import org.openhab.core.thing.ThingStatus;
34 import org.openhab.core.thing.ThingStatusDetail;
35 import org.openhab.core.types.Command;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
40 * The {@link DaliDeviceHandler} handles commands for things of type Device and Group.
42 * @author Robert Schmid - Initial contribution
45 public class DaliDt8DeviceHandler extends DaliDeviceHandler {
46 private final Logger logger = LoggerFactory.getLogger(DaliDt8DeviceHandler.class);
48 public DaliDt8DeviceHandler(Thing thing) {
53 public void handleCommand(ChannelUID channelUID, Command command) {
55 final DaliserverBridgeHandler daliHandler = getBridgeHandler();
56 if (CHANNEL_COLOR_TEMPERATURE.equals(channelUID.getId())) {
58 if (THING_TYPE_DEVICE_DT8.equals(this.thing.getThingTypeUID())) {
59 address = DaliAddress.createShortAddress(targetId);
60 } else if (THING_TYPE_GROUP_DT8.equals(this.thing.getThingTypeUID())) {
61 address = DaliAddress.createGroupAddress(targetId);
63 throw new DaliException("unknown device type");
65 if (command instanceof DecimalType) {
66 // Color temperature in DALI is represented in mirek ("reciprocal megakelvin")
67 // It is one million times the reciprocal of the color temperature (in Kelvin)
68 final int mirek = (int) (1E6f
69 / (Math.min(Math.max(((DecimalType) command).intValue(), 1000), 20000)));
70 final byte mirekLsb = (byte) (mirek & 0xff);
71 final byte mirekMsb = (byte) ((mirek >> 8) & 0xff);
72 // Write mirek value to the DTR0+DTR1 registers
73 daliHandler.sendCommand(DaliStandardCommand.createSetDTR0Command(mirekLsb));
74 daliHandler.sendCommand(DaliStandardCommand.createSetDTR1Command(mirekMsb));
75 // Indicate that the follwing command is a DT8 (WW/CW and single-channel RGB) command
76 daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
77 // Set the color temperature to the value in DTR0+DTR1
78 daliHandler.sendCommand(DaliStandardCommand.createSetColorTemperatureCommand(address));
79 // Finish the command sequence
80 daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
81 daliHandler.sendCommand(DaliStandardCommand.createActivateCommand(address));
85 DaliAddress readAddress = address;
86 if (readDeviceTargetId != null) {
87 readAddress = DaliAddress.createShortAddress(readDeviceTargetId);
89 // Write argument 0xc2 (query temporary color temperature) to DTR0 and set DT8
90 daliHandler.sendCommand(DaliStandardCommand.createSetDTR0Command(0xc2));
91 daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
92 // Mirek MSB is returned as result
93 CompletableFuture<@Nullable NumericMask> responseMsb = daliHandler.sendCommandWithResponse(
94 DaliStandardCommand.createQueryColorValueCommand(readAddress), DaliResponse.NumericMask.class);
95 daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
96 // Mirek LSB is written to DTR0
97 CompletableFuture<@Nullable NumericMask> responseLsb = daliHandler.sendCommandWithResponse(
98 DaliStandardCommand.createQueryContentDTR0Command(readAddress), DaliResponse.NumericMask.class);
100 CompletableFuture.allOf(responseMsb, responseLsb).thenAccept(x -> {
102 NumericMask msb = responseMsb.join(), lsb = responseLsb.join();
103 if (msb != null && !msb.mask && lsb != null && !lsb.mask) {
104 final int msbValue = msb.value != null ? msb.value : 0;
105 final int lsbValue = lsb.value != null ? lsb.value : 0;
106 final int mirek = ((msbValue & 0xff) << 8) | (lsbValue & 0xff);
107 final int kelvin = (int) (1E6f / mirek);
108 updateState(channelUID, new DecimalType(kelvin));
110 }).exceptionally(e -> {
111 logger.warn("Error querying device status: {}", e.getMessage());
115 } else if (CHANNEL_COLOR.equals(channelUID.getId())) {
117 if (THING_TYPE_DEVICE_DT8.equals(this.thing.getThingTypeUID())) {
118 address = DaliAddress.createShortAddress(targetId);
119 } else if (THING_TYPE_GROUP_DT8.equals(this.thing.getThingTypeUID())) {
120 address = DaliAddress.createGroupAddress(targetId);
122 throw new DaliException("unknown device type");
124 if (command instanceof HSBType) {
125 PercentType[] rgb = ((HSBType) command).toRGB();
126 final int r = (int) (254 * (rgb[0].floatValue() / 100));
127 final int g = (int) (254 * (rgb[1].floatValue() / 100));
128 final int b = (int) (254 * (rgb[2].floatValue() / 100));
129 logger.trace("RGB: {} {} {}", r, g, b);
130 // Write RGB values to the DTR0+DTR1+DTR2 registers
131 daliHandler.sendCommand(DaliStandardCommand.createSetDTR0Command(r));
132 daliHandler.sendCommand(DaliStandardCommand.createSetDTR1Command(g));
133 daliHandler.sendCommand(DaliStandardCommand.createSetDTR2Command(b));
134 // Indicate that the following command is a DT8 (WW/CW and single-channel RGB) command
135 daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
136 // Set the color to the values in DTR0+DTR1+DTR2
137 daliHandler.sendCommand(DaliStandardCommand.createSetRgbDimlevelCommand(address));
138 // Finish the command sequence
139 daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
140 daliHandler.sendCommand(DaliStandardCommand.createActivateCommand(address));
143 DaliAddress readAddress = address;
144 if (readDeviceTargetId != null) {
145 readAddress = DaliAddress.createShortAddress(readDeviceTargetId);
147 // Write argument 0xE9 (query red dimlevel) to DTR0 and set DT8
148 daliHandler.sendCommand(DaliStandardCommand.createSetDTR0Command(0xe9));
149 daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
150 // Red component is returned as result
151 CompletableFuture<@Nullable NumericMask> responseRed = daliHandler.sendCommandWithResponse(
152 DaliStandardCommand.createQueryColorValueCommand(readAddress), DaliResponse.NumericMask.class);
153 // Write argument 0xEA (query green dimlevel) to DTR0 and set DT8
154 daliHandler.sendCommand(DaliStandardCommand.createSetDTR0Command(0xea));
155 daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
156 // Green component is returned as result
157 CompletableFuture<@Nullable NumericMask> responseGreen = daliHandler.sendCommandWithResponse(
158 DaliStandardCommand.createQueryColorValueCommand(readAddress), DaliResponse.NumericMask.class);
159 // Write argument 0xEB (query blue dimlevel) to DTR0 and set DT8
160 daliHandler.sendCommand(DaliStandardCommand.createSetDTR0Command(0xeb));
161 daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
162 // Blue component is returned as result
163 CompletableFuture<@Nullable NumericMask> responseBlue = daliHandler.sendCommandWithResponse(
164 DaliStandardCommand.createQueryColorValueCommand(readAddress), DaliResponse.NumericMask.class);
166 CompletableFuture.allOf(responseRed, responseGreen, responseBlue).thenAccept(x -> {
168 NumericMask r = responseRed.join(), g = responseGreen.join(), b = responseBlue.join();
169 if (r != null && !r.mask && g != null && !g.mask && b != null && !b.mask) {
170 final int rValue = r.value != null ? r.value : 0;
171 final int gValue = g.value != null ? g.value : 0;
172 final int bValue = b.value != null ? b.value : 0;
173 updateState(channelUID, HSBType.fromRGB(rValue, gValue, bValue));
175 }).exceptionally(e -> {
176 logger.warn("Error querying device status: {}", e.getMessage());
181 super.handleCommand(channelUID, command);
183 } catch (DaliException e) {
184 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());