2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.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.library.types.QuantityType;
32 import org.openhab.core.library.unit.Units;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.ThingStatus;
36 import org.openhab.core.thing.ThingStatusDetail;
37 import org.openhab.core.types.Command;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
42 * The {@link DaliDeviceHandler} handles commands for things of type Device and Group.
44 * @author Robert Schmid - Initial contribution
47 public class DaliDt8DeviceHandler extends DaliDeviceHandler {
48 private final Logger logger = LoggerFactory.getLogger(DaliDt8DeviceHandler.class);
50 public DaliDt8DeviceHandler(Thing thing) {
55 public void handleCommand(ChannelUID channelUID, Command command) {
57 final DaliserverBridgeHandler daliHandler = getBridgeHandler();
58 if (CHANNEL_COLOR_TEMPERATURE.equals(channelUID.getId())) {
60 if (THING_TYPE_DEVICE_DT8.equals(this.thing.getThingTypeUID())) {
61 address = DaliAddress.createShortAddress(targetId);
62 } else if (THING_TYPE_GROUP_DT8.equals(this.thing.getThingTypeUID())) {
63 address = DaliAddress.createGroupAddress(targetId);
65 throw new DaliException("unknown device type");
68 if (command instanceof DecimalType) {
69 // Color temperature in DALI is represented in mirek ("reciprocal megakelvin")
70 // It is one million times the reciprocal of the color temperature (in Kelvin)
71 mirek = (int) (1E6f / (Math.min(Math.max(((DecimalType) command).intValue(), 1000), 20000)));
72 } else if (command instanceof QuantityType) {
73 // ensure it's in the correct units
74 QuantityType<?> commandQuantity = ((QuantityType) command).toInvertibleUnit(Units.MIRED);
75 if (commandQuantity == null) {
76 logger.warn("Unable to convert command {} to mireks", command);
79 mirek = commandQuantity.intValue();
81 logger.warn("Unable to convert command {} to mireks", command);
85 final byte mirekLsb = (byte) (mirek & 0xff);
86 final byte mirekMsb = (byte) ((mirek >> 8) & 0xff);
87 // Write mirek value to the DTR0+DTR1 registers
88 daliHandler.sendCommand(DaliStandardCommand.createSetDTR0Command(mirekLsb));
89 daliHandler.sendCommand(DaliStandardCommand.createSetDTR1Command(mirekMsb));
90 // Indicate that the follwing command is a DT8 (WW/CW and single-channel RGB) command
91 daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
92 // Set the color temperature to the value in DTR0+DTR1
93 daliHandler.sendCommand(DaliStandardCommand.createSetColorTemperatureCommand(address));
94 // Finish the command sequence
95 daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
96 daliHandler.sendCommand(DaliStandardCommand.createActivateCommand(address));
98 DaliAddress readAddress = address;
99 if (readDeviceTargetId != null) {
100 readAddress = DaliAddress.createShortAddress(readDeviceTargetId);
102 // Write argument 0xc2 (query temporary color temperature) to DTR0 and set DT8
103 daliHandler.sendCommand(DaliStandardCommand.createSetDTR0Command(0xc2));
104 daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
105 // Mirek MSB is returned as result
106 CompletableFuture<@Nullable NumericMask> responseMsb = daliHandler.sendCommandWithResponse(
107 DaliStandardCommand.createQueryColorValueCommand(readAddress), DaliResponse.NumericMask.class);
108 daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
109 // Mirek LSB is written to DTR0
110 CompletableFuture<@Nullable NumericMask> responseLsb = daliHandler.sendCommandWithResponse(
111 DaliStandardCommand.createQueryContentDTR0Command(readAddress), DaliResponse.NumericMask.class);
113 CompletableFuture.allOf(responseMsb, responseLsb).thenAccept(x -> {
115 NumericMask msb = responseMsb.join(), lsb = responseLsb.join();
116 if (msb != null && !msb.mask && lsb != null && !lsb.mask) {
117 final int msbValue = msb.value != null ? msb.value : 0;
118 final int lsbValue = lsb.value != null ? lsb.value : 0;
119 final int mirekState = ((msbValue & 0xff) << 8) | (lsbValue & 0xff);
120 final int kelvin = (int) (1E6f / mirekState);
121 updateState(channelUID, new QuantityType(kelvin, Units.KELVIN));
123 }).exceptionally(e -> {
124 logger.warn("Error querying device status: {}", e.getMessage());
128 } else if (CHANNEL_COLOR.equals(channelUID.getId())) {
130 if (THING_TYPE_DEVICE_DT8.equals(this.thing.getThingTypeUID())) {
131 address = DaliAddress.createShortAddress(targetId);
132 } else if (THING_TYPE_GROUP_DT8.equals(this.thing.getThingTypeUID())) {
133 address = DaliAddress.createGroupAddress(targetId);
135 throw new DaliException("unknown device type");
137 if (command instanceof HSBType) {
138 PercentType[] rgb = ((HSBType) command).toRGB();
139 final int r = (int) (254 * (rgb[0].floatValue() / 100));
140 final int g = (int) (254 * (rgb[1].floatValue() / 100));
141 final int b = (int) (254 * (rgb[2].floatValue() / 100));
142 logger.trace("RGB: {} {} {}", r, g, b);
143 // Write RGB values to the DTR0+DTR1+DTR2 registers
144 daliHandler.sendCommand(DaliStandardCommand.createSetDTR0Command(r));
145 daliHandler.sendCommand(DaliStandardCommand.createSetDTR1Command(g));
146 daliHandler.sendCommand(DaliStandardCommand.createSetDTR2Command(b));
147 // Indicate that the following command is a DT8 (WW/CW and single-channel RGB) command
148 daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
149 // Set the color to the values in DTR0+DTR1+DTR2
150 daliHandler.sendCommand(DaliStandardCommand.createSetRgbDimlevelCommand(address));
151 // Finish the command sequence
152 daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
153 daliHandler.sendCommand(DaliStandardCommand.createActivateCommand(address));
156 DaliAddress readAddress = address;
157 if (readDeviceTargetId != null) {
158 readAddress = DaliAddress.createShortAddress(readDeviceTargetId);
160 // Write argument 0xE9 (query red dimlevel) to DTR0 and set DT8
161 daliHandler.sendCommand(DaliStandardCommand.createSetDTR0Command(0xe9));
162 daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
163 // Red component is returned as result
164 CompletableFuture<@Nullable NumericMask> responseRed = daliHandler.sendCommandWithResponse(
165 DaliStandardCommand.createQueryColorValueCommand(readAddress), DaliResponse.NumericMask.class);
166 // Write argument 0xEA (query green dimlevel) to DTR0 and set DT8
167 daliHandler.sendCommand(DaliStandardCommand.createSetDTR0Command(0xea));
168 daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
169 // Green component is returned as result
170 CompletableFuture<@Nullable NumericMask> responseGreen = daliHandler.sendCommandWithResponse(
171 DaliStandardCommand.createQueryColorValueCommand(readAddress), DaliResponse.NumericMask.class);
172 // Write argument 0xEB (query blue dimlevel) to DTR0 and set DT8
173 daliHandler.sendCommand(DaliStandardCommand.createSetDTR0Command(0xeb));
174 daliHandler.sendCommand(DaliStandardCommand.createSetDeviceTypeCommand(8));
175 // Blue component is returned as result
176 CompletableFuture<@Nullable NumericMask> responseBlue = daliHandler.sendCommandWithResponse(
177 DaliStandardCommand.createQueryColorValueCommand(readAddress), DaliResponse.NumericMask.class);
179 CompletableFuture.allOf(responseRed, responseGreen, responseBlue).thenAccept(x -> {
181 NumericMask r = responseRed.join(), g = responseGreen.join(), b = responseBlue.join();
182 if (r != null && !r.mask && g != null && !g.mask && b != null && !b.mask) {
183 final int rValue = r.value != null ? r.value : 0;
184 final int gValue = g.value != null ? g.value : 0;
185 final int bValue = b.value != null ? b.value : 0;
186 updateState(channelUID, HSBType.fromRGB(rValue, gValue, bValue));
188 }).exceptionally(e -> {
189 logger.warn("Error querying device status: {}", e.getMessage());
194 super.handleCommand(channelUID, command);
196 } catch (DaliException e) {
197 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());