[enocean] Add second action for two rocker switches (Fixes #9750) (#10769)

* Added new channel:
   * secondActionPressed: Indicates if second action of rocker switch is pressed too
* Added two new profiles for channel rockerSwitchAction:
   * rockerswitchaction-toggle-switch
   * rockerswitchaction-toggle-player
* EnOceanSensorHandler can now handle extensible channels too
* EEP F6-02 refactoring

Also-by: Dietmar Franzen <dfranzen@fb3.fra-uas.de>
Signed-off-by: Daniel Weber <uni@fruggy.de>
This commit is contained in:
CountBigBang
2021-10-12 21:01:57 +02:00
committed by GitHub
parent cc92423d90
commit 4b13a26f15
17 changed files with 655 additions and 163 deletions

View File

@@ -132,6 +132,9 @@ public class EnOceanBindingConstants {
public static final String CHANNEL_ROCKERSWITCH_CHANNELA = "rockerswitchA";
public static final String CHANNEL_ROCKERSWITCH_CHANNELB = "rockerswitchB";
public static final String CHANNEL_ROCKERSWITCH_ACTION = "rockerSwitchAction";
public static final ChannelTypeUID CHANNELTYPE_ROCKERSWITCH_ACTION_UID = new ChannelTypeUID(BINDING_ID,
CHANNEL_ROCKERSWITCH_ACTION);
public static final String CHANNEL_VIRTUALSWITCHA = "virtualSwitchA";
public static final String CHANNEL_VIRTUALROLLERSHUTTERA = "virtualRollershutterA";
@@ -357,6 +360,9 @@ public class EnOceanBindingConstants {
Map.entry(CHANNEL_ROCKERSWITCH_CHANNELB,
new EnOceanChannelDescription(DefaultSystemChannelTypeProvider.SYSTEM_RAWROCKER.getUID(), null,
"Rocker Switch - Channel B", false, false)),
Map.entry(CHANNEL_ROCKERSWITCH_ACTION,
new EnOceanChannelDescription(CHANNELTYPE_ROCKERSWITCH_ACTION_UID, null, "Rocker Switch Action",
false, false)),
Map.entry(CHANNEL_VIRTUALSWITCHA,
new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_VIRTUALSWITCHA),

View File

@@ -19,9 +19,11 @@ package org.openhab.binding.enocean.internal.config;
public class EnOceanChannelRockerSwitchListenerConfig extends EnOceanChannelRockerSwitchConfigBase {
public String enoceanId;
public boolean handleSecondAction;
public EnOceanChannelRockerSwitchListenerConfig() {
super();
enoceanId = null;
handleSecondAction = false;
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.enocean.internal.config;
/**
* This {@link EnOceanProfileRockerSwitchActionConfig} config class is used for rockerSwitchAction profiles to define in
* which case it should react.
*
* @author Daniel Weber - Initial contribution
*/
public class EnOceanProfileRockerSwitchActionConfig {
public String channelAFilter;
public String channelBFilter;
public EnOceanProfileRockerSwitchActionConfig() {
channelAFilter = "*";
channelBFilter = "*";
}
}

View File

@@ -179,13 +179,14 @@ public enum EEPType {
CHANNEL_DOUBLEPRESS, CHANNEL_LONGPRESS, CHANNEL_BATTERY_LEVEL),
RockerSwitch2RockerStyle1(RORG.RPS, 0x02, 0x01, false, F6_02_01.class, THING_TYPE_ROCKERSWITCH,
CHANNEL_ROCKERSWITCH_CHANNELA, CHANNEL_ROCKERSWITCH_CHANNELB, CHANNEL_VIRTUALSWITCHA,
CHANNEL_VIRTUALROLLERSHUTTERA, CHANNEL_VIRTUALROCKERSWITCHB, CHANNEL_ROCKERSWITCHLISTENERSWITCH,
CHANNEL_ROCKERSWITCHLISTENERROLLERSHUTTER),
CHANNEL_ROCKERSWITCH_CHANNELA, CHANNEL_ROCKERSWITCH_CHANNELB, CHANNEL_ROCKERSWITCH_ACTION,
CHANNEL_VIRTUALSWITCHA, CHANNEL_VIRTUALROLLERSHUTTERA, CHANNEL_VIRTUALROCKERSWITCHB,
CHANNEL_ROCKERSWITCHLISTENERSWITCH, CHANNEL_ROCKERSWITCHLISTENERROLLERSHUTTER),
RockerSwitch2RockerStyle2(RORG.RPS, 0x02, 0x02, false, F6_02_02.class, THING_TYPE_ROCKERSWITCH,
CHANNEL_ROCKERSWITCH_CHANNELA, CHANNEL_ROCKERSWITCH_CHANNELB, CHANNEL_VIRTUALSWITCHA,
CHANNEL_VIRTUALROLLERSHUTTERA, CHANNEL_VIRTUALROCKERSWITCHB, CHANNEL_ROCKERSWITCHLISTENERSWITCH,
CHANNEL_ROCKERSWITCHLISTENERROLLERSHUTTER),
CHANNEL_ROCKERSWITCH_CHANNELA, CHANNEL_ROCKERSWITCH_CHANNELB, CHANNEL_ROCKERSWITCH_ACTION,
CHANNEL_VIRTUALSWITCHA, CHANNEL_VIRTUALROLLERSHUTTERA, CHANNEL_VIRTUALROCKERSWITCHB,
CHANNEL_ROCKERSWITCHLISTENERSWITCH, CHANNEL_ROCKERSWITCHLISTENERROLLERSHUTTER),
MechanicalHandle00(RORG.RPS, 0x10, 0x00, false, F6_10_00.class, THING_TYPE_MECHANICALHANDLE,
CHANNEL_WINDOWHANDLESTATE, CHANNEL_CONTACT),

View File

@@ -0,0 +1,178 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.enocean.internal.eep.F6_02;
import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;
import org.openhab.binding.enocean.internal.config.EnOceanChannelRockerSwitchConfigBase.SwitchMode;
import org.openhab.binding.enocean.internal.eep.Base._RPSMessage;
import org.openhab.binding.enocean.internal.messages.ERP1Message;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.UpDownType;
import org.openhab.core.thing.CommonTriggerEvents;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
*
* @author Daniel Weber - Initial contribution
*/
public abstract class F6_02 extends _RPSMessage {
final byte AI = 0;
final byte A0 = 1;
final byte BI = 2;
final byte B0 = 3;
final byte PRESSED = 16;
final byte PRESSED_SEC = 1;
final String DIR1 = "DIR1";
final String DIR2 = "DIR2";
final String NODIR = "-";
int secondByte = -1;
int secondStatus = -1;
public F6_02() {
super();
}
public F6_02(ERP1Message packet) {
super(packet);
}
private String getChannelADir() {
if ((bytes[0] >>> 5) == A0 && (bytes[0] & PRESSED) != 0) {
return DIR1;
} else if ((bytes[0] >>> 5) == AI && (bytes[0] & PRESSED) != 0) {
return DIR2;
} else {
return NODIR;
}
}
private String getChannelBDir() {
if ((bytes[0] >>> 5) == B0 && (bytes[0] & PRESSED) != 0) {
return DIR1;
} else if ((bytes[0] >>> 5) == BI && (bytes[0] & PRESSED) != 0) {
return DIR2;
} else if (((bytes[0] & 0xf) >>> 1) == B0 && (bytes[0] & PRESSED_SEC) != 0) {
return DIR1;
} else if (((bytes[0] & 0xf) >>> 1) == BI && (bytes[0] & PRESSED_SEC) != 0) {
return DIR2;
} else {
return NODIR;
}
}
protected String getRockerSwitchAction(Configuration config) {
String dirA = getChannelADir();
String dirB = getChannelBDir();
return dirA + "|" + dirB;
}
protected String getChannelEvent(byte dir1, byte dir2) {
if ((bytes[0] & PRESSED_SEC) != 0) {
// Do not emit an event if channelA is pressed together with channelB as it is undetermined which one gets
// fired first
return null;
} else if ((bytes[0] >>> 5) == dir1) {
return ((bytes[0] & PRESSED) != 0) ? CommonTriggerEvents.DIR1_PRESSED : CommonTriggerEvents.DIR1_RELEASED;
} else if ((bytes[0] >>> 5) == dir2) {
return ((bytes[0] & PRESSED) != 0) ? CommonTriggerEvents.DIR2_PRESSED : CommonTriggerEvents.DIR2_RELEASED;
} else {
return null;
}
}
protected State getState(byte dir1, byte dir2, boolean handleSecondAction, SwitchMode switchMode,
String channelTypeId, State currentState) {
// We are just listening on the pressed event here
switch (switchMode) {
case RockerSwitch:
if ((bytes[0] >>> 5) == dir1) {
if (((bytes[0] & PRESSED) != 0)) {
return channelTypeId.equals(CHANNEL_ROCKERSWITCHLISTENERSWITCH) ? OnOffType.ON : UpDownType.UP;
}
} else if ((bytes[0] >>> 5) == dir2) {
if (((bytes[0] & PRESSED) != 0)) {
return channelTypeId.equals(CHANNEL_ROCKERSWITCHLISTENERSWITCH) ? OnOffType.OFF
: UpDownType.DOWN;
}
} else if (handleSecondAction && ((bytes[0] & 0xf) >>> 1) == dir1) {
if (((bytes[0] & PRESSED_SEC) != 0)) {
return channelTypeId.equals(CHANNEL_ROCKERSWITCHLISTENERSWITCH) ? OnOffType.ON : UpDownType.UP;
}
} else if (handleSecondAction && ((bytes[0] & 0xf) >>> 1) == dir2) {
if (((bytes[0] & PRESSED_SEC) != 0)) {
return channelTypeId.equals(CHANNEL_ROCKERSWITCHLISTENERSWITCH) ? OnOffType.OFF
: UpDownType.DOWN;
}
}
break;
case ToggleDir1:
if ((bytes[0] >>> 5) == dir1) {
if (((bytes[0] & PRESSED) != 0)) {
return channelTypeId.equals(CHANNEL_ROCKERSWITCHLISTENERSWITCH)
? (currentState == UnDefType.UNDEF ? OnOffType.ON : inverse((OnOffType) currentState))
: (currentState == UnDefType.UNDEF ? UpDownType.UP
: inverse((UpDownType) currentState));
}
} else if (handleSecondAction && ((bytes[0] & 0xf) >>> 1) == dir1) {
if (((bytes[0] & PRESSED_SEC) != 0)) {
return channelTypeId.equals(CHANNEL_ROCKERSWITCHLISTENERSWITCH)
? (currentState == UnDefType.UNDEF ? OnOffType.ON : inverse((OnOffType) currentState))
: (currentState == UnDefType.UNDEF ? UpDownType.UP
: inverse((UpDownType) currentState));
}
}
break;
case ToggleDir2:
if ((bytes[0] >>> 5) == dir2) {
if (((bytes[0] & PRESSED) != 0)) {
return channelTypeId.equals(CHANNEL_ROCKERSWITCHLISTENERSWITCH)
? (currentState == UnDefType.UNDEF ? OnOffType.ON : inverse((OnOffType) currentState))
: (currentState == UnDefType.UNDEF ? UpDownType.UP
: inverse((UpDownType) currentState));
}
} else if (handleSecondAction && ((bytes[0] & 0xf) >>> 1) == dir2) {
if (((bytes[0] & PRESSED_SEC) != 0)) {
return channelTypeId.equals(CHANNEL_ROCKERSWITCHLISTENERSWITCH)
? (currentState == UnDefType.UNDEF ? OnOffType.ON : inverse((OnOffType) currentState))
: (currentState == UnDefType.UNDEF ? UpDownType.UP
: inverse((UpDownType) currentState));
}
}
break;
default:
break;
}
return UnDefType.UNDEF;
}
protected State inverse(OnOffType currentState) {
return currentState == OnOffType.ON ? OnOffType.OFF : OnOffType.ON;
}
protected State inverse(UpDownType currentState) {
return currentState == UpDownType.UP ? UpDownType.DOWN : UpDownType.UP;
}
@Override
protected boolean validateData(byte[] bytes) {
return super.validateData(bytes) && !getBit(bytes[0], 7);
}
}

View File

@@ -17,13 +17,11 @@ import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;
import java.util.function.Function;
import org.openhab.binding.enocean.internal.config.EnOceanChannelRockerSwitchConfigBase.Channel;
import org.openhab.binding.enocean.internal.config.EnOceanChannelVirtualRockerSwitchConfig;
import org.openhab.binding.enocean.internal.config.EnOceanChannelRockerSwitchListenerConfig;
import org.openhab.binding.enocean.internal.eep.Base._RPSMessage;
import org.openhab.binding.enocean.internal.messages.ERP1Message;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.types.UpDownType;
import org.openhab.core.thing.CommonTriggerEvents;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
@@ -33,16 +31,7 @@ import org.openhab.core.types.UnDefType;
*
* @author Daniel Weber - Initial contribution
*/
public class F6_02_01 extends _RPSMessage {
final byte AI = 0;
final byte A0 = 1;
final byte BI = 2;
final byte B0 = 3;
final byte PRESSED = 16;
int secondByte = -1;
int secondStatus = -1;
public class F6_02_01 extends F6_02 {
public F6_02_01() {
super();
@@ -57,21 +46,23 @@ public class F6_02_01 extends _RPSMessage {
Configuration config) {
if (t21 && nu) {
byte dir1 = channelId.equals(CHANNEL_ROCKERSWITCH_CHANNELA) ? A0 : B0;
byte dir2 = channelId.equals(CHANNEL_ROCKERSWITCH_CHANNELA) ? AI : BI;
if (CHANNEL_ROCKERSWITCH_ACTION.equals(channelTypeId)) {
return getRockerSwitchAction(config);
} else {
byte dir1 = channelId.equals(CHANNEL_ROCKERSWITCH_CHANNELA) ? A0 : B0;
byte dir2 = channelId.equals(CHANNEL_ROCKERSWITCH_CHANNELA) ? AI : BI;
if ((bytes[0] >>> 5) == dir1) {
return ((bytes[0] & PRESSED) != 0) ? CommonTriggerEvents.DIR1_PRESSED
: CommonTriggerEvents.DIR1_RELEASED;
} else if ((bytes[0] >>> 5) == dir2) {
return ((bytes[0] & PRESSED) != 0) ? CommonTriggerEvents.DIR2_PRESSED
: CommonTriggerEvents.DIR2_RELEASED;
return getChannelEvent(dir1, dir2);
}
} else if (t21 && !nu) {
if (lastEvent != null && lastEvent.equals(CommonTriggerEvents.DIR1_PRESSED)) {
return CommonTriggerEvents.DIR1_RELEASED;
} else if (lastEvent != null && lastEvent.equals(CommonTriggerEvents.DIR2_PRESSED)) {
return CommonTriggerEvents.DIR2_RELEASED;
if (CHANNEL_ROCKERSWITCH_ACTION.equals(channelTypeId)) {
return CommonTriggerEvents.RELEASED;
} else {
if (lastEvent != null && lastEvent.equals(CommonTriggerEvents.DIR1_PRESSED)) {
return CommonTriggerEvents.DIR1_RELEASED;
} else if (lastEvent != null && lastEvent.equals(CommonTriggerEvents.DIR2_PRESSED)) {
return CommonTriggerEvents.DIR2_RELEASED;
}
}
}
@@ -82,7 +73,7 @@ public class F6_02_01 extends _RPSMessage {
protected void convertFromCommandImpl(String channelId, String channelTypeId, Command command,
Function<String, State> getCurrentStateFunc, Configuration config) {
if (command instanceof StringType) {
StringType s = (StringType) command;
String s = ((StringType) command).toString();
if (s.equals(CommonTriggerEvents.DIR1_RELEASED) || s.equals(CommonTriggerEvents.DIR2_RELEASED)) {
setStatus(_RPSMessage.T21Flag);
@@ -106,72 +97,20 @@ public class F6_02_01 extends _RPSMessage {
@Override
protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) {
// this method is used by the classic device listener channels to convert an rocker switch message into an
// this method is used by the classic device listener channels to convert a rocker switch message into an
// appropriate item update
State currentState = getCurrentStateFunc.apply(channelId);
if (t21 && nu) {
EnOceanChannelVirtualRockerSwitchConfig c = config.as(EnOceanChannelVirtualRockerSwitchConfig.class);
EnOceanChannelRockerSwitchListenerConfig c = config.as(EnOceanChannelRockerSwitchListenerConfig.class);
byte dir1 = c.getChannel() == Channel.ChannelA ? A0 : B0;
byte dir2 = c.getChannel() == Channel.ChannelA ? AI : BI;
// We are just listening on the pressed event here
switch (c.getSwitchMode()) {
case RockerSwitch:
if ((bytes[0] >>> 5) == dir1) {
if (((bytes[0] & PRESSED) != 0)) {
return channelTypeId.equals(CHANNEL_ROCKERSWITCHLISTENERSWITCH) ? OnOffType.ON
: UpDownType.UP;
}
} else if ((bytes[0] >>> 5) == dir2) {
if (((bytes[0] & PRESSED) != 0)) {
return channelTypeId.equals(CHANNEL_ROCKERSWITCHLISTENERSWITCH) ? OnOffType.OFF
: UpDownType.DOWN;
}
}
break;
case ToggleDir1:
if ((bytes[0] >>> 5) == dir1) {
if (((bytes[0] & PRESSED) != 0)) {
return channelTypeId.equals(CHANNEL_ROCKERSWITCHLISTENERSWITCH)
? (currentState == UnDefType.UNDEF ? OnOffType.ON
: inverse((OnOffType) currentState))
: (currentState == UnDefType.UNDEF ? UpDownType.UP
: inverse((UpDownType) currentState));
}
}
break;
case ToggleDir2:
if ((bytes[0] >>> 5) == dir2) {
if (((bytes[0] & PRESSED) != 0)) {
return channelTypeId.equals(CHANNEL_ROCKERSWITCHLISTENERSWITCH)
? (currentState == UnDefType.UNDEF ? OnOffType.ON
: inverse((OnOffType) currentState))
: (currentState == UnDefType.UNDEF ? UpDownType.UP
: inverse((UpDownType) currentState));
}
}
break;
default:
break;
}
return getState(dir1, dir2, c.handleSecondAction, c.getSwitchMode(), channelTypeId, currentState);
}
return UnDefType.UNDEF;
}
private State inverse(OnOffType currentState) {
return currentState == OnOffType.ON ? OnOffType.OFF : OnOffType.ON;
}
private State inverse(UpDownType currentState) {
return currentState == UpDownType.UP ? UpDownType.DOWN : UpDownType.UP;
}
@Override
protected boolean validateData(byte[] bytes) {
return super.validateData(bytes) && !getBit(bytes[0], 7);
}
@Override
public boolean isValidForTeachIn() {
if (t21) {

View File

@@ -17,13 +17,11 @@ import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;
import java.util.function.Function;
import org.openhab.binding.enocean.internal.config.EnOceanChannelRockerSwitchConfigBase.Channel;
import org.openhab.binding.enocean.internal.config.EnOceanChannelVirtualRockerSwitchConfig;
import org.openhab.binding.enocean.internal.config.EnOceanChannelRockerSwitchListenerConfig;
import org.openhab.binding.enocean.internal.eep.Base._RPSMessage;
import org.openhab.binding.enocean.internal.messages.ERP1Message;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.types.UpDownType;
import org.openhab.core.thing.CommonTriggerEvents;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
@@ -33,13 +31,7 @@ import org.openhab.core.types.UnDefType;
*
* @author Daniel Weber - Initial contribution
*/
public class F6_02_02 extends _RPSMessage {
final byte AI = 0;
final byte A0 = 1;
final byte BI = 2;
final byte B0 = 3;
final byte PRESSED = 16;
public class F6_02_02 extends F6_02 {
public F6_02_02() {
super();
@@ -54,21 +46,22 @@ public class F6_02_02 extends _RPSMessage {
Configuration config) {
if (t21 && nu) {
byte dir1 = channelId.equals(CHANNEL_ROCKERSWITCH_CHANNELA) ? AI : BI;
byte dir2 = channelId.equals(CHANNEL_ROCKERSWITCH_CHANNELA) ? A0 : B0;
if ((bytes[0] >>> 5) == dir1) {
return ((bytes[0] & PRESSED) != 0) ? CommonTriggerEvents.DIR1_PRESSED
: CommonTriggerEvents.DIR1_RELEASED;
} else if ((bytes[0] >>> 5) == dir2) {
return ((bytes[0] & PRESSED) != 0) ? CommonTriggerEvents.DIR2_PRESSED
: CommonTriggerEvents.DIR2_RELEASED;
if (CHANNEL_ROCKERSWITCH_ACTION.equals(channelTypeId)) {
return getRockerSwitchAction(config);
} else {
byte dir1 = channelId.equals(CHANNEL_ROCKERSWITCH_CHANNELA) ? AI : BI;
byte dir2 = channelId.equals(CHANNEL_ROCKERSWITCH_CHANNELA) ? A0 : B0;
return getChannelEvent(dir1, dir2);
}
} else if (t21 && !nu) {
if (lastEvent != null && lastEvent.equals(CommonTriggerEvents.DIR1_PRESSED)) {
return CommonTriggerEvents.DIR1_RELEASED;
} else if (lastEvent != null && lastEvent.equals(CommonTriggerEvents.DIR2_PRESSED)) {
return CommonTriggerEvents.DIR2_RELEASED;
if (CHANNEL_ROCKERSWITCH_ACTION.equals(channelTypeId)) {
return CommonTriggerEvents.RELEASED;
} else {
if (lastEvent != null && lastEvent.equals(CommonTriggerEvents.DIR1_PRESSED)) {
return CommonTriggerEvents.DIR1_RELEASED;
} else if (lastEvent != null && lastEvent.equals(CommonTriggerEvents.DIR2_PRESSED)) {
return CommonTriggerEvents.DIR2_RELEASED;
}
}
}
@@ -79,7 +72,7 @@ public class F6_02_02 extends _RPSMessage {
protected void convertFromCommandImpl(String channelId, String channelTypeId, Command command,
Function<String, State> getCurrentStateFunc, Configuration config) {
if (command instanceof StringType) {
StringType s = (StringType) command;
String s = ((StringType) command).toString();
if (s.equals(CommonTriggerEvents.DIR1_RELEASED) || s.equals(CommonTriggerEvents.DIR2_RELEASED)) {
setStatus(_RPSMessage.T21Flag);
@@ -103,67 +96,20 @@ public class F6_02_02 extends _RPSMessage {
@Override
protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) {
// this method is used by the classic device listener channels to convert an rocker switch message into an
// this method is used by the classic device listener channels to convert a rocker switch message into an
// appropriate item update
State currentState = getCurrentStateFunc.apply(channelId);
if (t21 && nu) {
EnOceanChannelVirtualRockerSwitchConfig c = config.as(EnOceanChannelVirtualRockerSwitchConfig.class);
EnOceanChannelRockerSwitchListenerConfig c = config.as(EnOceanChannelRockerSwitchListenerConfig.class);
byte dir1 = c.getChannel() == Channel.ChannelA ? AI : BI;
byte dir2 = c.getChannel() == Channel.ChannelA ? A0 : B0;
// We are just listening on the pressed event here
switch (c.getSwitchMode()) {
case RockerSwitch:
if ((bytes[0] >>> 5) == dir1) {
if (((bytes[0] & PRESSED) != 0)) {
return channelTypeId.equals(CHANNEL_ROCKERSWITCHLISTENERSWITCH) ? OnOffType.ON
: UpDownType.UP;
}
} else if ((bytes[0] >>> 5) == dir2) {
if (((bytes[0] & PRESSED) != 0)) {
return channelTypeId.equals(CHANNEL_ROCKERSWITCHLISTENERSWITCH) ? OnOffType.OFF
: UpDownType.DOWN;
}
}
break;
case ToggleDir1:
if ((bytes[0] >>> 5) == dir1) {
if (((bytes[0] & PRESSED) != 0)) {
return channelTypeId.equals(CHANNEL_ROCKERSWITCHLISTENERSWITCH)
? (currentState == UnDefType.UNDEF ? OnOffType.ON
: inverse((OnOffType) currentState))
: (currentState == UnDefType.UNDEF ? UpDownType.UP
: inverse((UpDownType) currentState));
}
}
break;
case ToggleDir2:
if ((bytes[0] >>> 5) == dir2) {
if (((bytes[0] & PRESSED) != 0)) {
return channelTypeId.equals(CHANNEL_ROCKERSWITCHLISTENERSWITCH)
? (currentState == UnDefType.UNDEF ? OnOffType.ON
: inverse((OnOffType) currentState))
: (currentState == UnDefType.UNDEF ? UpDownType.UP
: inverse((UpDownType) currentState));
}
}
break;
default:
break;
}
return getState(dir1, dir2, c.handleSecondAction, c.getSwitchMode(), channelTypeId, currentState);
}
return UnDefType.UNDEF;
}
private State inverse(OnOffType currentState) {
return currentState == OnOffType.ON ? OnOffType.OFF : OnOffType.ON;
}
private State inverse(UpDownType currentState) {
return currentState == UpDownType.UP ? UpDownType.DOWN : UpDownType.UP;
}
@Override
public boolean isValidForTeachIn() {
return false; // Never treat a message as F6-02-02, let user decide which orientation of rocker switch is used

View File

@@ -129,7 +129,8 @@ public class EnOceanBaseSensorHandler extends EnOceanBaseThingHandler implements
protected Predicate<Channel> channelFilter(EEPType eepType, byte[] senderId) {
return c -> {
boolean result = eepType.GetSupportedChannels().containsKey(c.getUID().getId());
boolean result = eepType.isChannelSupported(c);
return (isLinked(c.getUID()) || c.getKind() == ChannelKind.TRIGGER) && result;
};
}

View File

@@ -0,0 +1,109 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.enocean.internal.profiles;
import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;
import static org.openhab.binding.enocean.internal.profiles.EnOceanProfiles.*;
import java.util.Collection;
import java.util.Collections;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.CoreItemFactory;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.profiles.Profile;
import org.openhab.core.thing.profiles.ProfileAdvisor;
import org.openhab.core.thing.profiles.ProfileCallback;
import org.openhab.core.thing.profiles.ProfileContext;
import org.openhab.core.thing.profiles.ProfileFactory;
import org.openhab.core.thing.profiles.ProfileType;
import org.openhab.core.thing.profiles.ProfileTypeProvider;
import org.openhab.core.thing.profiles.ProfileTypeUID;
import org.openhab.core.thing.type.ChannelType;
import org.openhab.core.thing.type.ChannelTypeRegistry;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link EnOceanProfileFactory} class creates EnOceanProfiles
*
* @author Daniel Weber - Initial contribution
*/
@Component
@NonNullByDefault
public class EnOceanProfileFactory implements ProfileFactory, ProfileAdvisor, ProfileTypeProvider {
private static final Set<ProfileType> SUPPORTED_PROFILE_TYPES = Set.of(ROCKERSWITCHACTION_TOGGLE_SWITCH_TYPE,
ROCKERSWITCHACTION_TOGGLE_PLAYER_TYPE);
private static final Set<ProfileTypeUID> SUPPORTED_PROFILE_TYPE_UIDS = Set.of(ROCKERSWITCHACTION_TOGGLE_SWITCH,
ROCKERSWITCHACTION_TOGGLE_PLAYER);
private final ChannelTypeRegistry channelTypeRegistry;
@Activate
public EnOceanProfileFactory(final @Reference ChannelTypeRegistry channelTypeRegistry) {
this.channelTypeRegistry = channelTypeRegistry;
}
@Override
public Collection<ProfileType> getProfileTypes(@Nullable Locale locale) {
return Collections.unmodifiableList(SUPPORTED_PROFILE_TYPES.stream().collect(Collectors.toList()));
}
@Override
public @Nullable ProfileTypeUID getSuggestedProfileTypeUID(Channel channel, @Nullable String itemType) {
ChannelType channelType = channelTypeRegistry.getChannelType(channel.getChannelTypeUID());
if (channelType == null) {
return null;
}
return getSuggestedProfileTypeUID(channelType, itemType);
}
@Override
public @Nullable ProfileTypeUID getSuggestedProfileTypeUID(ChannelType channelType, @Nullable String itemType) {
if (CHANNELTYPE_ROCKERSWITCH_ACTION_UID.equals(channelType.getUID())) {
if (CoreItemFactory.PLAYER.equalsIgnoreCase(itemType)) {
return ROCKERSWITCHACTION_TOGGLE_PLAYER;
} else if (CoreItemFactory.SWITCH.equalsIgnoreCase(itemType)) {
return ROCKERSWITCHACTION_TOGGLE_SWITCH;
}
}
return null;
}
@Override
public @Nullable Profile createProfile(ProfileTypeUID profileTypeUID, ProfileCallback callback,
ProfileContext profileContext) {
if (ROCKERSWITCHACTION_TOGGLE_PLAYER.equals(profileTypeUID)) {
return new RockerSwitchActionTogglePlayerProfile(callback, profileContext);
} else if (ROCKERSWITCHACTION_TOGGLE_SWITCH.equals(profileTypeUID)) {
return new RockerSwitchActionToggleSwitchProfile(callback, profileContext);
} else {
return null;
}
}
@Override
public Collection<ProfileTypeUID> getSupportedProfileTypeUIDs() {
return Collections.unmodifiableList(SUPPORTED_PROFILE_TYPE_UIDS.stream().collect(Collectors.toList()));
}
}

View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.enocean.internal.profiles;
import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.CoreItemFactory;
import org.openhab.core.thing.profiles.ProfileType;
import org.openhab.core.thing.profiles.ProfileTypeBuilder;
import org.openhab.core.thing.profiles.ProfileTypeUID;
/**
* The {@link EnOceanProfiles} class defines profile constants
*
* @author Daniel Weber - Initial contribution
*/
@NonNullByDefault
public class EnOceanProfiles {
public static final ProfileTypeUID ROCKERSWITCHACTION_TOGGLE_SWITCH = new ProfileTypeUID(BINDING_ID,
"rockerswitchaction-toggle-switch");
public static final ProfileTypeUID ROCKERSWITCHACTION_TOGGLE_PLAYER = new ProfileTypeUID(BINDING_ID,
"rockerswitchaction-toggle-player");
static final ProfileType ROCKERSWITCHACTION_TOGGLE_SWITCH_TYPE = ProfileTypeBuilder
.newTrigger(ROCKERSWITCHACTION_TOGGLE_SWITCH, "Rocker Switch Action Toggle Switch")
.withSupportedItemTypes(CoreItemFactory.SWITCH)
.withSupportedChannelTypeUIDs(CHANNELTYPE_ROCKERSWITCH_ACTION_UID).build();
static final ProfileType ROCKERSWITCHACTION_TOGGLE_PLAYER_TYPE = ProfileTypeBuilder
.newTrigger(ROCKERSWITCHACTION_TOGGLE_PLAYER, "Rocker Switch Action Toggle Player")
.withSupportedItemTypes(CoreItemFactory.PLAYER)
.withSupportedChannelTypeUIDs(CHANNELTYPE_ROCKERSWITCH_ACTION_UID).build();
}

View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.enocean.internal.profiles;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.enocean.internal.config.EnOceanProfileRockerSwitchActionConfig;
import org.openhab.core.thing.profiles.ProfileCallback;
import org.openhab.core.thing.profiles.ProfileContext;
import org.openhab.core.thing.profiles.TriggerProfile;
import org.openhab.core.types.State;
/**
* @author Daniel Weber - Initial contribution
*/
@NonNullByDefault
public abstract class RockerSwitchActionBaseProfile implements TriggerProfile {
protected final ProfileCallback callback;
protected ProfileContext context;
protected @Nullable State previousState;
final String ANYDIR = "*";
public RockerSwitchActionBaseProfile(ProfileCallback callback, ProfileContext context) {
this.callback = callback;
this.context = context;
}
protected boolean isEventValid(String event) {
String[] directions = event.split("\\|");
if (directions.length != 2) {
return false;
}
EnOceanProfileRockerSwitchActionConfig config = context.getConfiguration()
.as(EnOceanProfileRockerSwitchActionConfig.class);
if (!(config.channelAFilter.equals(ANYDIR) || config.channelAFilter.equals(directions[0]))) {
return false;
} else if (!(config.channelBFilter.equals(ANYDIR) || config.channelBFilter.equals(directions[1]))) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.enocean.internal.profiles;
import static org.openhab.binding.enocean.internal.profiles.EnOceanProfiles.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.PlayPauseType;
import org.openhab.core.thing.CommonTriggerEvents;
import org.openhab.core.thing.profiles.ProfileCallback;
import org.openhab.core.thing.profiles.ProfileContext;
import org.openhab.core.thing.profiles.ProfileTypeUID;
import org.openhab.core.types.State;
/**
* The {@link RockerSwitchActionTogglePlayerProfile} is used for channel rockerSwitchAction to be able to bind this
* trigger channel directly to a player item
*
* @author Daniel Weber - Initial contribution
*/
@NonNullByDefault
public class RockerSwitchActionTogglePlayerProfile extends RockerSwitchActionBaseProfile {
public RockerSwitchActionTogglePlayerProfile(ProfileCallback callback, ProfileContext context) {
super(callback, context);
}
@Override
public ProfileTypeUID getProfileTypeUID() {
return ROCKERSWITCHACTION_TOGGLE_PLAYER;
}
@Override
public void onStateUpdateFromItem(State state) {
previousState = state.as(PlayPauseType.class);
}
@Override
public void onTriggerFromHandler(String event) {
// Ignore released event
if (!CommonTriggerEvents.RELEASED.equals(event) && isEventValid(event)) {
PlayPauseType newState = PlayPauseType.PLAY.equals(previousState) ? PlayPauseType.PAUSE
: PlayPauseType.PLAY;
callback.sendCommand(newState);
previousState = newState;
}
}
}

View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.enocean.internal.profiles;
import static org.openhab.binding.enocean.internal.profiles.EnOceanProfiles.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.CommonTriggerEvents;
import org.openhab.core.thing.profiles.ProfileCallback;
import org.openhab.core.thing.profiles.ProfileContext;
import org.openhab.core.thing.profiles.ProfileTypeUID;
import org.openhab.core.types.State;
/**
* The {@link RockerSwitchActionToggleSwitchProfile} is used for channel rockerSwitchAction to be able to bind this
* trigger channel directly to a switch item
*
* @author Daniel Weber - Initial contribution
*/
@NonNullByDefault
public class RockerSwitchActionToggleSwitchProfile extends RockerSwitchActionBaseProfile {
public RockerSwitchActionToggleSwitchProfile(ProfileCallback callback, ProfileContext context) {
super(callback, context);
}
@Override
public ProfileTypeUID getProfileTypeUID() {
return ROCKERSWITCHACTION_TOGGLE_SWITCH;
}
@Override
public void onStateUpdateFromItem(State state) {
previousState = state.as(OnOffType.class);
}
@Override
public void onTriggerFromHandler(String event) {
// Ignore released event
if (!CommonTriggerEvents.RELEASED.equals(event) && isEventValid(event)) {
OnOffType newState = OnOffType.ON.equals(previousState) ? OnOffType.OFF : OnOffType.ON;
callback.sendCommand(newState);
previousState = newState;
}
}
}

View File

@@ -29,6 +29,11 @@
</options>
<default>channelA</default>
</parameter>
<parameter name="handleSecondAction" type="boolean">
<label>Handle Second Action</label>
<description>React if selected channel is pressed as second action too</description>
<default>false</default>
</parameter>
<parameter name="switchMode" type="text">
<label>Switch Mode</label>
<options>
@@ -56,4 +61,27 @@
</parameter>
</config-description>
<config-description uri="profile:enocean:rockerswitchaction-toggle-switch">
<parameter name="channelAFilter" type="text">
<label>Channel A Filter</label>
<default>*</default>
<options>
<option value="*">Any Direction</option>
<option value="DIR1">Direction 1</option>
<option value="DIR2">Direction 2</option>
<option value="-">No Direction</option>
</options>
</parameter>
<parameter name="channelBFilter" type="text">
<label>Channel B Filter</label>
<default>*</default>
<options>
<option value="*">Any Direction</option>
<option value="DIR1">Direction 1</option>
<option value="DIR2">Direction 2</option>
<option value="-">No Direction</option>
</options>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -20,6 +20,9 @@
<channel typeId="system.rawrocker" id="rockerswitchB">
<label>Rocker Switch - Channel B</label>
</channel>
<channel id="rockerSwitchAction" typeId="rockerSwitchAction">
<label>Rocker Switch Action</label>
</channel>
</channels>
<config-description>

View File

@@ -810,4 +810,23 @@
<state min="0" max="100" pattern="%.1f rpm" readOnly="true"/>
</channel-type>
<channel-type id="rockerSwitchAction">
<kind>trigger</kind>
<label>Rocker Switch Action</label>
<description>Is triggered when a certain combination of rockers is pressed or released.</description>
<event>
<options>
<option value="DIR1|-"></option>
<option value="DIR2|-"></option>
<option value="DIR1|DIR1"></option>
<option value="DIR1|DIR2"></option>
<option value="DIR2|DIR1"></option>
<option value="DIR2|DIR2"></option>
<option value="-|DIR1"></option>
<option value="-|DIR2"></option>
<option value="RELEASED"></option>
</options>
</event>
</channel-type>
</thing:thing-descriptions>