2 * Copyright (c) 2010-2024 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.rfxcom.internal.messages;
15 import static org.openhab.binding.rfxcom.internal.RFXComBindingConstants.*;
16 import static org.openhab.binding.rfxcom.internal.messages.ByteEnumUtil.fromByte;
17 import static org.openhab.binding.rfxcom.internal.messages.RFXComLighting5Message.SubType.*;
19 import java.math.BigDecimal;
20 import java.math.RoundingMode;
21 import java.util.Arrays;
22 import java.util.List;
24 import org.openhab.binding.rfxcom.internal.config.RFXComDeviceConfiguration;
25 import org.openhab.binding.rfxcom.internal.exceptions.RFXComException;
26 import org.openhab.binding.rfxcom.internal.exceptions.RFXComInvalidStateException;
27 import org.openhab.binding.rfxcom.internal.exceptions.RFXComUnsupportedChannelException;
28 import org.openhab.binding.rfxcom.internal.exceptions.RFXComUnsupportedValueException;
29 import org.openhab.binding.rfxcom.internal.handler.DeviceState;
30 import org.openhab.core.library.types.DecimalType;
31 import org.openhab.core.library.types.IncreaseDecreaseType;
32 import org.openhab.core.library.types.OnOffType;
33 import org.openhab.core.library.types.OpenClosedType;
34 import org.openhab.core.library.types.PercentType;
35 import org.openhab.core.library.types.StringType;
36 import org.openhab.core.types.State;
37 import org.openhab.core.types.Type;
38 import org.openhab.core.types.UnDefType;
41 * RFXCOM data class for lighting5 message.
43 * @author Paul Hampson, Neil Renaud - Initial contribution
44 * @author Pauli Anttila
45 * @author Martin van Wingerden - added support for IT and some other subtypes
47 public class RFXComLighting5Message extends RFXComDeviceMessageImpl<RFXComLighting5Message.SubType> {
49 public enum SubType implements ByteEnumWrapper {
67 private final int subType;
69 SubType(int subType) {
70 this.subType = subType;
74 public byte toByte() {
75 return (byte) subType;
80 * Note: for the lighting5 commands, some command are only supported for certain sub types and
81 * command-bytes might even have a different meaning for another sub type.
83 * If no sub types are specified for a command, its supported by all sub types.
84 * An example is the command OFF which is represented by the byte 0x00 for all subtypes.
86 * Otherwise the list of sub types after the command-bytes indicates the sub types
87 * which support this command with this byte.
88 * Example byte value 0x03 means GROUP_ON for IT and some others while it means MOOD1 for LIGHTWAVERF
90 public enum Commands implements ByteEnumWrapperWithSupportedSubTypes<SubType> {
93 GROUP_OFF(0x02, LIGHTWAVERF, BBSB_NEW, CONRAD_RSL2, EURODOMEST, AVANTEK, IT, KANGTAI),
95 GROUP_ON(0x03, BBSB_NEW, CONRAD_RSL2, EURODOMEST, AVANTEK, IT, KANGTAI),
96 MOOD1(0x03, LIGHTWAVERF),
97 MOOD2(0x04, LIGHTWAVERF),
98 MOOD3(0x05, LIGHTWAVERF),
99 MOOD4(0x06, LIGHTWAVERF),
100 MOOD5(0x07, LIGHTWAVERF),
101 RESERVED1(0x08, LIGHTWAVERF),
102 RESERVED2(0x09, LIGHTWAVERF),
103 UNLOCK(0x0A, LIGHTWAVERF),
104 LOCK(0x0B, LIGHTWAVERF),
105 ALL_LOCK(0x0C, LIGHTWAVERF),
106 CLOSE_RELAY(0x0D, LIGHTWAVERF),
107 STOP_RELAY(0x0E, LIGHTWAVERF),
108 OPEN_RELAY(0x0F, LIGHTWAVERF),
109 SET_LEVEL(0x10, LIGHTWAVERF, IT),
110 COLOUR_PALETTE(0x11, LIGHTWAVERF),
111 COLOUR_TONE(0x12, LIGHTWAVERF),
112 COLOUR_CYCLE(0x13, LIGHTWAVERF),
113 TOGGLE_1(0x01, LIVOLO_APPLIANCE),
114 TOGGLE_2(0x02, LIVOLO_APPLIANCE),
115 TOGGLE_3(0x03, LIVOLO_APPLIANCE),
116 TOGGLE_4(0x04, LIVOLO_APPLIANCE),
117 TOGGLE_5(0x07, LIVOLO_APPLIANCE),
118 TOGGLE_6(0x0B, LIVOLO_APPLIANCE),
119 TOGGLE_7(0x06, LIVOLO_APPLIANCE),
120 TOGGLE_8(0x0A, LIVOLO_APPLIANCE),
121 TOGGLE_9(0x05, LIVOLO_APPLIANCE);
123 private final int command;
124 private final List<SubType> supportedBySubTypes;
126 Commands(int command) {
127 this(command, SubType.values());
130 Commands(int command, SubType... supportedBySubTypes) {
131 this.command = command;
132 this.supportedBySubTypes = Arrays.asList(supportedBySubTypes);
136 public List<SubType> supportedBySubTypes() {
137 return supportedBySubTypes;
141 public byte toByte() {
142 return (byte) command;
146 public SubType subType;
148 public byte unitCode;
149 public Commands command;
150 public byte dimmingLevel;
152 public RFXComLighting5Message() {
153 super(PacketType.LIGHTING5);
156 public RFXComLighting5Message(byte[] data) throws RFXComException {
161 public String toString() {
164 str += super.toString();
165 str += ", Sub type = " + subType;
166 str += ", Device Id = " + getDeviceId();
167 str += ", Command = " + command;
168 str += ", Dim level = " + dimmingLevel;
169 str += ", Signal level = " + signalLevel;
175 public void encodeMessage(byte[] data) throws RFXComException {
176 super.encodeMessage(data);
178 subType = fromByte(SubType.class, super.subType);
180 sensorId = (data[4] & 0xFF) << 16 | (data[5] & 0xFF) << 8 | (data[6] & 0xFF);
183 command = fromByte(Commands.class, data[8], subType);
185 dimmingLevel = data[9];
186 signalLevel = (byte) ((data[10] & 0xF0) >> 4);
190 public byte[] decodeMessage() {
191 byte[] data = new byte[11];
194 data[1] = RFXComBaseMessage.PacketType.LIGHTING5.toByte();
195 data[2] = subType.toByte();
197 data[4] = (byte) ((sensorId >> 16) & 0xFF);
198 data[5] = (byte) ((sensorId >> 8) & 0xFF);
199 data[6] = (byte) (sensorId & 0xFF);
202 data[8] = command.toByte();
203 data[9] = dimmingLevel;
204 data[10] = (byte) ((signalLevel & 0x0F) << 4);
210 public String getDeviceId() {
211 return sensorId + ID_DELIMITER + unitCode;
215 * Convert a 0-31 scale value to a percent type.
217 * @param pt percent type to convert
218 * @return converted value 0-31
220 public static int getDimLevelFromPercentType(PercentType pt) {
221 return pt.toBigDecimal().multiply(BigDecimal.valueOf(31))
222 .divide(PercentType.HUNDRED.toBigDecimal(), 0, RoundingMode.UP).intValue();
226 * Convert a 0-31 scale value to a percent type.
228 * @param value percent type to convert
229 * @return converted value 0-31
231 public static PercentType getPercentTypeFromDimLevel(int value) {
232 value = Math.min(value, 31);
234 return new PercentType(BigDecimal.valueOf(value).multiply(BigDecimal.valueOf(100))
235 .divide(BigDecimal.valueOf(31), 0, RoundingMode.UP).intValue());
239 public State convertToState(String channelId, RFXComDeviceConfiguration config, DeviceState deviceState)
240 throws RFXComUnsupportedChannelException, RFXComInvalidStateException {
245 return new DecimalType(0);
247 return new DecimalType(1);
249 return new DecimalType(2);
251 return new DecimalType(3);
253 return new DecimalType(4);
255 return new DecimalType(5);
257 throw new RFXComUnsupportedChannelException(
258 "Unexpected mood command: " + command + " for " + channelId);
261 case CHANNEL_DIMMING_LEVEL:
262 return RFXComLighting5Message.getPercentTypeFromDimLevel(dimmingLevel);
264 case CHANNEL_COMMAND:
268 return OnOffType.OFF;
276 throw new RFXComUnsupportedChannelException("Can't convert " + command + " for " + channelId);
279 case CHANNEL_COMMAND_STRING:
280 return command == null ? UnDefType.UNDEF : StringType.valueOf(command.toString());
282 case CHANNEL_CONTACT:
286 return OpenClosedType.CLOSED;
290 return OpenClosedType.OPEN;
294 throw new RFXComUnsupportedChannelException("Can't convert " + command + " for " + channelId);
298 return super.convertToState(channelId, config, deviceState);
303 public void setSubType(SubType subType) {
304 this.subType = subType;
308 public void setDeviceId(String deviceId) throws RFXComException {
309 String[] ids = deviceId.split("\\" + ID_DELIMITER);
310 if (ids.length != 2) {
311 throw new RFXComException("Invalid device id '" + deviceId + "'");
314 sensorId = Integer.parseInt(ids[0]);
315 unitCode = Byte.parseByte(ids[1]);
319 public void convertFromState(String channelId, Type type) throws RFXComUnsupportedChannelException {
321 case CHANNEL_COMMAND:
322 if (type instanceof OnOffType) {
323 command = (type == OnOffType.ON ? Commands.ON : Commands.OFF);
327 throw new RFXComUnsupportedChannelException("Channel " + channelId + " does not accept " + type);
331 case CHANNEL_COMMAND_STRING:
332 command = Commands.valueOf(type.toString().toUpperCase());
335 case CHANNEL_DIMMING_LEVEL:
336 if (type instanceof OnOffType) {
337 command = (type == OnOffType.ON ? Commands.ON : Commands.OFF);
340 } else if (type instanceof PercentType percentCommand) {
341 command = Commands.SET_LEVEL;
342 dimmingLevel = (byte) getDimLevelFromPercentType(percentCommand);
344 if (dimmingLevel == 0) {
345 command = Commands.OFF;
348 } else if (type instanceof IncreaseDecreaseType) {
349 command = Commands.SET_LEVEL;
350 // Evert: I do not know how to get previous object state...
354 throw new RFXComUnsupportedChannelException("Channel " + channelId + " does not accept " + type);
359 throw new RFXComUnsupportedChannelException("Channel " + channelId + " is not relevant here");
364 public SubType convertSubType(String subType) throws RFXComUnsupportedValueException {
365 return ByteEnumUtil.convertSubType(SubType.class, subType);