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.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.RFXComFanMessage.Commands.*;
18 import static org.openhab.binding.rfxcom.internal.messages.RFXComFanMessage.SubType.*;
20 import java.util.Arrays;
21 import java.util.List;
23 import org.eclipse.jdt.annotation.Nullable;
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.OnOffType;
32 import org.openhab.core.library.types.StringType;
33 import org.openhab.core.library.types.UpDownType;
34 import org.openhab.core.types.State;
35 import org.openhab.core.types.Type;
36 import org.openhab.core.types.UnDefType;
39 * RFXCOM data class for fan message.
41 * @author Martin van Wingerden - initial contribution
43 public class RFXComFanMessage extends RFXComDeviceMessageImpl<RFXComFanMessage.SubType> {
45 public enum SubType implements ByteEnumWrapper {
50 WESTINGHOUSE_7226640(0x04),
55 LUCCI_AIR_DC_II(0x09),
56 ITHO_CVE_ECO_RFT(0x0A),
59 private final int subType;
61 SubType(int subType) {
62 this.subType = subType;
66 public byte toByte() {
67 return (byte) subType;
71 public enum Commands implements ByteEnumWrapperWithSupportedSubTypes<SubType> {
72 HI(1, WESTINGHOUSE_7226640, CASAFAN, LUCCI_AIR_FAN),
73 MED(2, WESTINGHOUSE_7226640, CASAFAN, LUCCI_AIR_FAN),
74 LOW(3, WESTINGHOUSE_7226640, CASAFAN, LUCCI_AIR_FAN),
75 OFF(4, WESTINGHOUSE_7226640, CASAFAN, LUCCI_AIR_FAN),
76 LIGHT(5, WESTINGHOUSE_7226640, CASAFAN, LUCCI_AIR_FAN),
78 FALMEC_POWER_OFF(1, 0, FALMEC),
79 FALMEC_SPEED_1(2, 1, FALMEC),
80 FALMEC_SPEED_2(3, 2, FALMEC),
81 FALMEC_SPEED_3(4, 3, FALMEC),
82 FALMEC_SPEED_4(5, 4, FALMEC),
83 FALMEC_TIMER_1(6, FALMEC),
84 FALMEC_TIMER_2(7, FALMEC),
85 FALMEC_TIMER_3(8, FALMEC),
86 FALMEC_TIMER_4(9, FALMEC),
87 FALMEC_LIGHT_ON(10, FALMEC),
88 FALMEC_LIGHT_OFF(11, FALMEC),
90 FT1211R_POWER(1, 0, FT1211R),
91 FT1211R_LIGHT(2, FT1211R),
92 FT1211R_SPEED_1(3, 1, FT1211R),
93 FT1211R_SPEED_2(4, 2, FT1211R),
94 FT1211R_SPEED_3(5, 3, FT1211R),
95 FT1211R_SPEED_4(6, 4, FT1211R),
96 FT1211R_SPEED_5(7, 5, FT1211R),
97 FT1211R_FORWARD_REVERSE(8, FT1211R),
98 FT1211R_TIMER_1H(9, FT1211R),
99 FT1211R_TIMER_4H(10, FT1211R),
100 FT1211R_TIMER_8H(11, FT1211R),
102 LUCCI_AIR_DC_POWER(1, LUCCI_AIR_DC),
103 LUCCI_AIR_DC_UP(2, LUCCI_AIR_DC),
104 LUCCI_AIR_DC_DOWN(3, LUCCI_AIR_DC),
105 LUCCI_AIR_DC_LIGHT(4, LUCCI_AIR_DC),
106 LUCCI_AIR_DC_REVERSE(5, LUCCI_AIR_DC),
107 LUCCI_AIR_DC_NATURAL_FLOW(6, LUCCI_AIR_DC),
108 LUCCI_AIR_DC_PAIR(7, LUCCI_AIR_DC),
109 LUCCI_AIR_DC_SPEED_1(8, 1, LUCCI_AIR_DC),
110 LUCCI_AIR_DC_SPEED_2(9, 2, LUCCI_AIR_DC),
111 LUCCI_AIR_DC_SPEED_3(10, 3, LUCCI_AIR_DC),
112 LUCCI_AIR_DC_SPEED_4(11, 4, LUCCI_AIR_DC),
113 LUCCI_AIR_DC_SPEED_5(12, 5, LUCCI_AIR_DC),
114 LUCCI_AIR_DC_SPEED_6(13, 6, LUCCI_AIR_DC),
116 LUCCI_AIR_DC_II_POWER_OFF(1, 0, LUCCI_AIR_DC_II),
117 LUCCI_AIR_DC_II_SPEED_1(2, 1, LUCCI_AIR_DC_II),
118 LUCCI_AIR_DC_II_SPEED_2(3, 2, LUCCI_AIR_DC_II),
119 LUCCI_AIR_DC_II_SPEED_3(4, 3, LUCCI_AIR_DC_II),
120 LUCCI_AIR_DC_II_SPEED_4(5, 4, LUCCI_AIR_DC_II),
121 LUCCI_AIR_DC_II_SPEED_5(6, 5, LUCCI_AIR_DC_II),
122 LUCCI_AIR_DC_II_SPEED_6(7, 6, LUCCI_AIR_DC_II),
123 LUCCI_AIR_DC_II_LIGHT(8, LUCCI_AIR_DC_II),
124 LUCCI_AIR_DC_II_REVERSE(9, LUCCI_AIR_DC_II),
131 NOVY_RESET_FILTER(6, NOVY);
133 private final int command;
134 private final Integer speed;
135 private final List<SubType> supportedBySubTypes;
137 Commands(int command, SubType... supportedSubType) {
138 this(command, null, supportedSubType);
141 Commands(int command, Integer speed, SubType... supportedSubType) {
142 this.command = command;
144 this.supportedBySubTypes = Arrays.asList(supportedSubType);
148 public static Commands bySpeed(SubType subType, int speed) {
149 for (Commands value : values()) {
150 if (value.supportedBySubTypes.contains(subType) && value.speed != null && value.speed == speed) {
158 public byte toByte() {
159 return (byte) command;
162 public Integer getSpeed() {
167 public List<SubType> supportedBySubTypes() {
168 return supportedBySubTypes;
172 private static final List<SubType> GENERIC_SUB_TYPES = Arrays.asList(WESTINGHOUSE_7226640, CASAFAN, LUCCI_AIR_FAN);
174 private static final List<Commands> LIGHT_ON_COMMANDS = Arrays.asList(LIGHT, LUCCI_AIR_DC_LIGHT,
175 LUCCI_AIR_DC_II_LIGHT, FALMEC_LIGHT_ON, NOVY_LIGHT);
176 private static final List<Commands> ON_COMMANDS = Arrays.asList(HI, MED, LOW, FALMEC_SPEED_1, FALMEC_SPEED_2,
177 FALMEC_SPEED_3, FALMEC_SPEED_4, LUCCI_AIR_DC_II_SPEED_1, LUCCI_AIR_DC_II_SPEED_2, LUCCI_AIR_DC_II_SPEED_3,
178 LUCCI_AIR_DC_II_SPEED_4, LUCCI_AIR_DC_II_SPEED_5, LUCCI_AIR_DC_II_SPEED_6, LUCCI_AIR_DC_SPEED_1,
179 LUCCI_AIR_DC_SPEED_2, LUCCI_AIR_DC_SPEED_3, LUCCI_AIR_DC_SPEED_4, LUCCI_AIR_DC_SPEED_5,
180 LUCCI_AIR_DC_SPEED_6);
181 private static final List<Commands> OFF_COMMANDS = Arrays.asList(OFF, FALMEC_POWER_OFF, LUCCI_AIR_DC_II_POWER_OFF);
183 private SubType subType;
184 private int sensorId;
185 private Commands command;
187 public RFXComFanMessage() {
188 super(PacketType.FAN);
191 public RFXComFanMessage(byte[] data) throws RFXComException {
196 public PacketType getPacketType() {
200 case WESTINGHOUSE_7226640:
201 return PacketType.FAN;
203 return PacketType.FAN_SF01;
205 return PacketType.FAN_ITHO;
207 return PacketType.FAN_SEAV;
209 return PacketType.FAN_LUCCI_DC;
211 return PacketType.FAN_FT1211R;
213 return PacketType.FAN_FALMEC;
214 case LUCCI_AIR_DC_II:
215 return PacketType.FAN_LUCCI_DC_II;
216 case ITHO_CVE_ECO_RFT:
217 return PacketType.FAN_ITHO_CVE_ECO_RFT;
219 return PacketType.FAN_NOVY;
221 return super.getPacketType();
225 public void encodeMessage(byte[] data) throws RFXComException {
226 super.encodeMessage(data);
228 subType = fromByte(SubType.class, super.subType);
229 sensorId = (data[4] & 0xFF) << 16 | (data[5] & 0xFF) << 8 | (data[6] & 0xFF);
230 command = fromByte(Commands.class, data[7], subType);
232 signalLevel = (byte) (data[8] & 0x0F);
236 public byte[] decodeMessage() {
237 byte[] data = new byte[9];
240 data[1] = PacketType.FAN.toByte();
241 data[2] = subType.toByte();
244 data[4] = (byte) ((sensorId >> 16) & 0xFF);
245 data[5] = (byte) ((sensorId >> 8) & 0xFF);
246 data[6] = (byte) (sensorId & 0xFF);
248 data[7] = command.toByte();
250 data[8] = (byte) (signalLevel & 0x0F);
256 public String getDeviceId() {
257 return String.valueOf(sensorId);
261 public void setSubType(SubType subType) {
262 this.subType = subType;
266 public void setDeviceId(String deviceId) {
267 sensorId = Integer.parseInt(deviceId);
271 public SubType convertSubType(String subType) throws RFXComUnsupportedValueException {
272 return ByteEnumUtil.convertSubType(SubType.class, subType);
276 public State convertToState(String channelId, RFXComDeviceConfiguration config, DeviceState deviceState)
277 throws RFXComUnsupportedChannelException, RFXComInvalidStateException {
279 case CHANNEL_FAN_LIGHT:
280 return handleLightChannel();
282 case CHANNEL_FAN_SPEED:
283 return handleFanSpeedChannel();
285 case CHANNEL_COMMAND:
286 return handleCommandChannel();
288 case CHANNEL_COMMAND_STRING:
289 return handleCommandStringChannel();
292 return super.convertToState(channelId, config, deviceState);
296 private State handleCommandStringChannel() {
297 if (command == null) {
298 return UnDefType.UNDEF;
300 return StringType.valueOf(command.toString().replace(subType.name() + "_", ""));
303 private State handleLightChannel() {
304 if (LIGHT_ON_COMMANDS.contains(command)) {
306 } else if (command == Commands.FALMEC_LIGHT_OFF) {
307 return OnOffType.OFF;
309 return UnDefType.UNDEF;
313 private State handleFanSpeedChannel() {
319 return StringType.valueOf(command.toString());
321 case FALMEC_POWER_OFF:
327 case FT1211R_SPEED_1:
328 case FT1211R_SPEED_2:
329 case FT1211R_SPEED_3:
330 case FT1211R_SPEED_4:
331 case FT1211R_SPEED_5:
332 case LUCCI_AIR_DC_SPEED_1:
333 case LUCCI_AIR_DC_SPEED_2:
334 case LUCCI_AIR_DC_SPEED_3:
335 case LUCCI_AIR_DC_SPEED_4:
336 case LUCCI_AIR_DC_SPEED_5:
337 case LUCCI_AIR_DC_SPEED_6:
338 case LUCCI_AIR_DC_II_POWER_OFF:
339 case LUCCI_AIR_DC_II_SPEED_1:
340 case LUCCI_AIR_DC_II_SPEED_2:
341 case LUCCI_AIR_DC_II_SPEED_3:
342 case LUCCI_AIR_DC_II_SPEED_4:
343 case LUCCI_AIR_DC_II_SPEED_5:
344 case LUCCI_AIR_DC_II_SPEED_6:
345 return new DecimalType(command.getSpeed());
347 case LUCCI_AIR_DC_DOWN:
349 return UpDownType.DOWN;
351 case LUCCI_AIR_DC_UP:
353 return UpDownType.UP;
360 private State handleCommandChannel() {
361 if (ON_COMMANDS.contains(command)) {
363 } else if (OFF_COMMANDS.contains(command)) {
364 return OnOffType.OFF;
371 public void convertFromState(String channelId, Type type) throws RFXComUnsupportedChannelException {
373 case CHANNEL_COMMAND:
374 command = handleCommand(channelId, type);
377 case CHANNEL_FAN_SPEED:
378 command = handleFanSpeedCommand(channelId, type);
381 case CHANNEL_FAN_LIGHT:
382 command = handleFanLightCommand(channelId, type);
385 case CHANNEL_COMMAND_STRING:
386 command = handleCommandString(channelId, type);
390 throw new RFXComUnsupportedChannelException("Channel " + channelId + " is not relevant here");
394 private Commands handleCommandString(String channelId, Type type) throws RFXComUnsupportedChannelException {
395 if (type instanceof StringType) {
396 String stringCommand = type.toString();
397 switch (stringCommand) {
414 return Commands.valueOf(subType.name() + "_" + stringCommand);
417 throw new RFXComUnsupportedChannelException("Channel " + channelId + " does not accept " + type);
420 private Commands handleCommand(String channelId, Type type) throws RFXComUnsupportedChannelException {
421 if (type instanceof OnOffType) {
422 if (GENERIC_SUB_TYPES.contains(subType)) {
423 return (type == OnOffType.ON ? Commands.MED : Commands.OFF);
424 } else if (subType == FALMEC) {
425 return (type == OnOffType.ON ? Commands.FALMEC_SPEED_2 : Commands.FALMEC_POWER_OFF);
426 } else if (subType == LUCCI_AIR_DC_II) {
427 return (type == OnOffType.ON ? LUCCI_AIR_DC_II_SPEED_3 : LUCCI_AIR_DC_II_POWER_OFF);
430 throw new RFXComUnsupportedChannelException("Channel " + channelId + " does not accept " + type);
433 private Commands handleFanSpeedCommand(String channelId, Type type) throws RFXComUnsupportedChannelException {
434 if (type instanceof StringType) {
435 String stringCommand = type.toString();
436 switch (stringCommand) {
441 return Commands.valueOf(stringCommand);
443 } else if (type instanceof DecimalType) {
444 Commands speedCommand = Commands.bySpeed(subType, ((DecimalType) type).intValue());
445 if (speedCommand != null) {
448 } else if (type instanceof UpDownType && subType == LUCCI_AIR_DC) {
449 if (UpDownType.UP == type) {
450 return Commands.LUCCI_AIR_DC_UP;
452 return Commands.LUCCI_AIR_DC_DOWN;
454 } else if (type instanceof UpDownType && subType == NOVY) {
455 if (UpDownType.UP == type) {
456 return Commands.NOVY_UP;
458 return Commands.NOVY_DOWN;
461 throw new RFXComUnsupportedChannelException("Channel " + channelId + " does not accept " + type);
464 private Commands handleFanLightCommand(String channelId, Type type) throws RFXComUnsupportedChannelException {
465 if (type == OnOffType.ON) {
469 case WESTINGHOUSE_7226640:
473 return FALMEC_LIGHT_ON;
475 case LUCCI_AIR_DC_II:
476 return LUCCI_AIR_DC_II_LIGHT;
481 } else if (type == OnOffType.OFF && subType == FALMEC) {
482 return Commands.FALMEC_LIGHT_OFF;
484 throw new RFXComUnsupportedChannelException("Channel " + channelId + " does not accept " + type);