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),
132 NOVY_MOOD_LIGHT(7, NOVY);
134 private final int command;
135 private final Integer speed;
136 private final List<SubType> supportedBySubTypes;
138 Commands(int command, SubType... supportedSubType) {
139 this(command, null, supportedSubType);
142 Commands(int command, Integer speed, SubType... supportedSubType) {
143 this.command = command;
145 this.supportedBySubTypes = Arrays.asList(supportedSubType);
149 public static Commands bySpeed(SubType subType, int speed) {
150 for (Commands value : values()) {
151 if (value.supportedBySubTypes.contains(subType) && value.speed != null && value.speed == speed) {
159 public byte toByte() {
160 return (byte) command;
163 public Integer getSpeed() {
168 public List<SubType> supportedBySubTypes() {
169 return supportedBySubTypes;
173 private static final List<SubType> GENERIC_SUB_TYPES = Arrays.asList(WESTINGHOUSE_7226640, CASAFAN, LUCCI_AIR_FAN);
175 private static final List<Commands> LIGHT_ON_COMMANDS = Arrays.asList(LIGHT, LUCCI_AIR_DC_LIGHT,
176 LUCCI_AIR_DC_II_LIGHT, FALMEC_LIGHT_ON, NOVY_LIGHT);
177 private static final List<Commands> ON_COMMANDS = Arrays.asList(HI, MED, LOW, FALMEC_SPEED_1, FALMEC_SPEED_2,
178 FALMEC_SPEED_3, FALMEC_SPEED_4, LUCCI_AIR_DC_II_SPEED_1, LUCCI_AIR_DC_II_SPEED_2, LUCCI_AIR_DC_II_SPEED_3,
179 LUCCI_AIR_DC_II_SPEED_4, LUCCI_AIR_DC_II_SPEED_5, LUCCI_AIR_DC_II_SPEED_6, LUCCI_AIR_DC_SPEED_1,
180 LUCCI_AIR_DC_SPEED_2, LUCCI_AIR_DC_SPEED_3, LUCCI_AIR_DC_SPEED_4, LUCCI_AIR_DC_SPEED_5,
181 LUCCI_AIR_DC_SPEED_6);
182 private static final List<Commands> OFF_COMMANDS = Arrays.asList(OFF, FALMEC_POWER_OFF, LUCCI_AIR_DC_II_POWER_OFF);
184 private SubType subType;
185 private int sensorId;
186 private Commands command;
188 public RFXComFanMessage() {
189 super(PacketType.FAN);
192 public RFXComFanMessage(byte[] data) throws RFXComException {
197 public PacketType getPacketType() {
201 case WESTINGHOUSE_7226640:
202 return PacketType.FAN;
204 return PacketType.FAN_SF01;
206 return PacketType.FAN_ITHO;
208 return PacketType.FAN_SEAV;
210 return PacketType.FAN_LUCCI_DC;
212 return PacketType.FAN_FT1211R;
214 return PacketType.FAN_FALMEC;
215 case LUCCI_AIR_DC_II:
216 return PacketType.FAN_LUCCI_DC_II;
217 case ITHO_CVE_ECO_RFT:
218 return PacketType.FAN_ITHO_CVE_ECO_RFT;
220 return PacketType.FAN_NOVY;
222 return super.getPacketType();
226 public void encodeMessage(byte[] data) throws RFXComException {
227 super.encodeMessage(data);
229 subType = fromByte(SubType.class, super.subType);
230 sensorId = (data[4] & 0xFF) << 16 | (data[5] & 0xFF) << 8 | (data[6] & 0xFF);
231 command = fromByte(Commands.class, data[7], subType);
233 signalLevel = (byte) (data[8] & 0x0F);
237 public byte[] decodeMessage() {
238 byte[] data = new byte[9];
241 data[1] = PacketType.FAN.toByte();
242 data[2] = subType.toByte();
245 data[4] = (byte) ((sensorId >> 16) & 0xFF);
246 data[5] = (byte) ((sensorId >> 8) & 0xFF);
247 data[6] = (byte) (sensorId & 0xFF);
249 data[7] = command.toByte();
251 data[8] = (byte) (signalLevel & 0x0F);
257 public String getDeviceId() {
258 return String.valueOf(sensorId);
262 public void setSubType(SubType subType) {
263 this.subType = subType;
267 public void setDeviceId(String deviceId) {
268 sensorId = Integer.parseInt(deviceId);
272 public SubType convertSubType(String subType) throws RFXComUnsupportedValueException {
273 return ByteEnumUtil.convertSubType(SubType.class, subType);
277 public State convertToState(String channelId, RFXComDeviceConfiguration config, DeviceState deviceState)
278 throws RFXComUnsupportedChannelException, RFXComInvalidStateException {
280 case CHANNEL_FAN_LIGHT:
281 return handleLightChannel();
283 case CHANNEL_FAN_SPEED:
284 return handleFanSpeedChannel();
286 case CHANNEL_COMMAND:
287 return handleCommandChannel();
289 case CHANNEL_COMMAND_STRING:
290 return handleCommandStringChannel();
293 return super.convertToState(channelId, config, deviceState);
297 private State handleCommandStringChannel() {
298 if (command == null) {
299 return UnDefType.UNDEF;
301 return StringType.valueOf(command.toString().replace(subType.name() + "_", ""));
304 private State handleLightChannel() {
305 if (LIGHT_ON_COMMANDS.contains(command)) {
307 } else if (command == Commands.FALMEC_LIGHT_OFF) {
308 return OnOffType.OFF;
310 return UnDefType.UNDEF;
314 private State handleFanSpeedChannel() {
320 return StringType.valueOf(command.toString());
322 case FALMEC_POWER_OFF:
328 case FT1211R_SPEED_1:
329 case FT1211R_SPEED_2:
330 case FT1211R_SPEED_3:
331 case FT1211R_SPEED_4:
332 case FT1211R_SPEED_5:
333 case LUCCI_AIR_DC_SPEED_1:
334 case LUCCI_AIR_DC_SPEED_2:
335 case LUCCI_AIR_DC_SPEED_3:
336 case LUCCI_AIR_DC_SPEED_4:
337 case LUCCI_AIR_DC_SPEED_5:
338 case LUCCI_AIR_DC_SPEED_6:
339 case LUCCI_AIR_DC_II_POWER_OFF:
340 case LUCCI_AIR_DC_II_SPEED_1:
341 case LUCCI_AIR_DC_II_SPEED_2:
342 case LUCCI_AIR_DC_II_SPEED_3:
343 case LUCCI_AIR_DC_II_SPEED_4:
344 case LUCCI_AIR_DC_II_SPEED_5:
345 case LUCCI_AIR_DC_II_SPEED_6:
346 return new DecimalType(command.getSpeed());
348 case LUCCI_AIR_DC_DOWN:
350 return UpDownType.DOWN;
352 case LUCCI_AIR_DC_UP:
354 return UpDownType.UP;
361 private State handleCommandChannel() {
362 if (ON_COMMANDS.contains(command)) {
364 } else if (OFF_COMMANDS.contains(command)) {
365 return OnOffType.OFF;
372 public void convertFromState(String channelId, Type type) throws RFXComUnsupportedChannelException {
374 case CHANNEL_COMMAND:
375 command = handleCommand(channelId, type);
378 case CHANNEL_FAN_SPEED:
379 command = handleFanSpeedCommand(channelId, type);
382 case CHANNEL_FAN_LIGHT:
383 command = handleFanLightCommand(channelId, type);
386 case CHANNEL_COMMAND_STRING:
387 command = handleCommandString(channelId, type);
391 throw new RFXComUnsupportedChannelException("Channel " + channelId + " is not relevant here");
395 private Commands handleCommandString(String channelId, Type type) throws RFXComUnsupportedChannelException {
396 if (type instanceof StringType) {
397 String stringCommand = type.toString();
398 switch (stringCommand) {
416 return Commands.valueOf(subType.name() + "_" + stringCommand);
419 throw new RFXComUnsupportedChannelException("Channel " + channelId + " does not accept " + type);
422 private Commands handleCommand(String channelId, Type type) throws RFXComUnsupportedChannelException {
423 if (type instanceof OnOffType) {
424 if (GENERIC_SUB_TYPES.contains(subType)) {
425 return (type == OnOffType.ON ? Commands.MED : Commands.OFF);
426 } else if (subType == FALMEC) {
427 return (type == OnOffType.ON ? Commands.FALMEC_SPEED_2 : Commands.FALMEC_POWER_OFF);
428 } else if (subType == LUCCI_AIR_DC_II) {
429 return (type == OnOffType.ON ? LUCCI_AIR_DC_II_SPEED_3 : LUCCI_AIR_DC_II_POWER_OFF);
432 throw new RFXComUnsupportedChannelException("Channel " + channelId + " does not accept " + type);
435 private Commands handleFanSpeedCommand(String channelId, Type type) throws RFXComUnsupportedChannelException {
436 if (type instanceof StringType) {
437 String stringCommand = type.toString();
438 switch (stringCommand) {
443 return Commands.valueOf(stringCommand);
445 } else if (type instanceof DecimalType) {
446 Commands speedCommand = Commands.bySpeed(subType, ((DecimalType) type).intValue());
447 if (speedCommand != null) {
450 } else if (type instanceof UpDownType && subType == LUCCI_AIR_DC) {
451 if (UpDownType.UP == type) {
452 return Commands.LUCCI_AIR_DC_UP;
454 return Commands.LUCCI_AIR_DC_DOWN;
456 } else if (type instanceof UpDownType && subType == NOVY) {
457 if (UpDownType.UP == type) {
458 return Commands.NOVY_UP;
460 return Commands.NOVY_DOWN;
463 throw new RFXComUnsupportedChannelException("Channel " + channelId + " does not accept " + type);
466 private Commands handleFanLightCommand(String channelId, Type type) throws RFXComUnsupportedChannelException {
467 if (type == OnOffType.ON) {
471 case WESTINGHOUSE_7226640:
475 return FALMEC_LIGHT_ON;
477 case LUCCI_AIR_DC_II:
478 return LUCCI_AIR_DC_II_LIGHT;
483 } else if (type == OnOffType.OFF && subType == FALMEC) {
484 return Commands.FALMEC_LIGHT_OFF;
486 throw new RFXComUnsupportedChannelException("Channel " + channelId + " does not accept " + type);