Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[enocean] Add support for Soda Handles (EEP D2-06-01) #11230

Merged
merged 20 commits into from
Oct 16, 2021
Merged
16 changes: 11 additions & 5 deletions bundles/org.openhab.binding.enocean/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ This binding is developed on and tested with the following devices
* Hoppe SecuSignal window handles
* Rocker switches (NodOn, Eltako FT55 etc)
* Siegenia Senso Secure window sensors
* Soda window handles

However, because of the standardized EnOcean protocol it is more important which EEP this binding supports.
Hence if your device supports one of the following EEPs the chances are good that your device is also supported by this binding.
Expand All @@ -77,7 +78,7 @@ Hence if your device supports one of the following EEPs the chances are good tha
| bridge | - | - | repeaterMode, setBaseId | USB300, EnOceanPi | - |
| pushButton | F6-01/D2-03 | 0x01/0x0A | pushButton, doublePress,<br/>longPress, batteryLevel | NodOn soft button | Manually/Discovery |
| rockerSwitch | F6-02 | 0x01-02 | rockerswitchA, rockerswitchB | Eltako FT55 | Discovery |
| mechanicalHandle | F6-10 | 0x00-01 | windowHandleState, contact | Hoppe SecuSignal handles, Eltako TF-FGB | Discovery |
| mechanicalHandle | F6-10/D2-06 | 0x00-01/0x01 | windowHandleState, contact and a lot more for soda handles³ | Hoppe SecuSignal handles, Eltako TF-FGB, Soda handles | Discovery |
| contact | D5-00 | 0x01 | contact | Eltako FTK(E) & TF-FKB | Discovery |
| temperatureSensor | A5-02 | 0x01-30 | temperature | Thermokon SR65 | Discovery |
| temperatureHumiditySensor | A5-04 | 0x01-03 | humidity, temperature | Eltako FTSB | Discovery |
Expand All @@ -99,6 +100,8 @@ Hence if your device supports one of the following EEPs the chances are good tha

² These are just examples of supported devices

³ Note that the soda handles potentially contain a wide range of different sensors and buttons. However the amount of built-in sensors and buttons may vary between different models. In case your particular device does not contain one of the potentially supported features the corresponding channel will never trigger an update. Please see the manual of your particular model to check which channels should be supported before opening an issue.

lolodomo marked this conversation as resolved.
Show resolved Hide resolved
Furthermore following supporting EEP family is available too: A5-11, types 0x03 (rollershutter position status), 0x04 (extended light status) and D0-06 (battery level indication).

A `rockerSwitch` is used to receive messages from a physical EnOcean Rocker Switch.
Expand Down Expand Up @@ -180,7 +183,7 @@ If you change the SenderId of your thing, you have to pair again the thing with
| | enoceanId | EnOceanId of device this thing belongs to | hex value as string |
| rockerSwitch | receivingEEPId | | F6_02_01, F6_02_02 |
| | enoceanId | | |
| mechanicalHandle | receivingEEPId | | F6_10_00, F6_10_01, A5_14_09 |
| mechanicalHandle | receivingEEPId | | F6_10_00, F6_10_01, A5_14_09, D2_06_01 |
| | enoceanId | | |
| | receivingSIGEEP | | |
| contact | receivingEEPId | | D5_00_01, A5_14_01_ELTAKO |
Expand Down Expand Up @@ -255,11 +258,12 @@ The channels of a thing are determined automatically based on the chosen EEP.
| repeaterMode | String | Set repeater level to 1, 2 or disable |
| setBaseId | String | Changes the BaseId of your gateway. This can only be done 10 times! So use it with care. |
| pushButton | Trigger | Channel type system:rawbutton, emits PRESSED and RELEASED events |
| pushButton2 | Trigger | Channel type system:rawbutton, emits PRESSED and RELEASED events |
| doublePress | Trigger | Channel type system:rawbutton, emits PRESSED |
| longPress | Trigger | Channel type system:rawbutton, emits PRESSED and RELEASED events |
| rockerswitchA/B | Trigger | Channel type system:rawrocker, emits DIR1_PRESSED, DIR1_RELEASED, DIR2_PRESSED, DIR2_RELEASED events |
| windowHandleState | String | Textual representation of handle position (OPEN, CLOSED, TILTED) |
| windowSashState | String | Textual representation of sash position (OPEN, CLOSED, TILTED) |
| windowHandleState | String | Textual representation of handle position (OPEN, CLOSED, TILTED or UP, DOWN, LEFT, RIGHT for the D2_06_01 EEP) |
| windowSashState | String | Textual representation of sash position (OPEN, CLOSED, TILTED or TILTED, NOT TILTED for the D2_06_01 EEP) |
lolodomo marked this conversation as resolved.
Show resolved Hide resolved
| windowCalibrationState | String | Textual representation of the calibration state (OK, ERROR, INVALID) |
| windowCalibrationStep | String | Textual representation of the next step that must be performed for calibrating the device (e.g. NONE, SASH CLOSED HANDLE CLOSED, SASH CLOSED HANDLE OPEN, SASH OPEN HANDLE TILTED, and so on) |
| contact | Contact | State OPEN/CLOSED (tilted handle => OPEN) |
Expand Down Expand Up @@ -324,7 +328,9 @@ The channels of a thing are determined automatically based on the chosen EEP.
| repeatCount | Number | Number of repeaters involved in the transmission of the telegram |
| lastReceived | DateTime | Date and time the last telegram was received |
| statusRequestEvent | Trigger | Emits event 'requestAnswer' |
| windowBreachEvent | Trigger | Emits event 'ALARM' |
| windowBreachEvent | Trigger | Emits event 'ALARM' |
| protectionPlusEvent | Trigger | Emits event 'ALARM' |
| vacationModeToggleEvent | Trigger | Emits events 'ACTIVATED', 'DEACTIVATED' |


Items linked to bi-directional actuators (actuator sends status messages back) should always disable the `autoupdate`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ public class EnOceanBindingConstants {
public static final String CHANNEL_INDOORAIRANALYSIS = "indoorAirAnalysis";

public static final String CHANNEL_PUSHBUTTON = "pushButton";
public static final String CHANNEL_PUSHBUTTON2 = "pushButton2";
public static final String CHANNEL_DOUBLEPRESS = "doublePress";
public static final String CHANNEL_LONGPRESS = "longPress";

Expand All @@ -145,6 +146,8 @@ public class EnOceanBindingConstants {
public static final String CHANNEL_WINDOWCALIBRATIONSTATE = "windowCalibrationState";
public static final String CHANNEL_WINDOWCALIBRATIONSTEP = "windowCalibrationStep";
public static final String CHANNEL_WINDOWBREACHEVENT = "windowBreachEvent";
public static final String CHANNEL_PROTECTIONPLUSEVENT = "protectionPlusEvent";
public static final String CHANNEL_VACATIONMODETOGGLEEVENT = "vacationModeToggleEvent";
public static final String CHANNEL_CONTACT = "contact";
public static final String CHANNEL_TEACHINCMD = "teachInCMD";
public static final String CHANNEL_INSTANTPOWER = "instantpower";
Expand Down Expand Up @@ -322,6 +325,12 @@ public class EnOceanBindingConstants {
Map.entry(CHANNEL_WINDOWBREACHEVENT,
new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_WINDOWBREACHEVENT), null, null,
false, true)),
Map.entry(CHANNEL_PROTECTIONPLUSEVENT,
new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_PROTECTIONPLUSEVENT), null,
null, false, true)),
Map.entry(CHANNEL_VACATIONMODETOGGLEEVENT,
new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_VACATIONMODETOGGLEEVENT), null,
null, false, true)),
Map.entry(
CHANNEL_BATTERY_VOLTAGE,
new EnOceanChannelDescription(
Expand All @@ -344,6 +353,9 @@ public class EnOceanBindingConstants {
Map.entry(CHANNEL_PUSHBUTTON,
new EnOceanChannelDescription(DefaultSystemChannelTypeProvider.SYSTEM_RAWBUTTON.getUID(), null,
"Push button", false, true)),
Map.entry(CHANNEL_PUSHBUTTON2,
new EnOceanChannelDescription(DefaultSystemChannelTypeProvider.SYSTEM_RAWBUTTON.getUID(), null,
"Push button 2", false, true)),
Map.entry(CHANNEL_DOUBLEPRESS,
new EnOceanChannelDescription(DefaultSystemChannelTypeProvider.SYSTEM_RAWBUTTON.getUID(), null,
"Double press", false, true)),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/**
* 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.D2_06;

import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;

import java.util.function.Function;

import org.openhab.binding.enocean.internal.eep.Base._VLDMessage;
import org.openhab.binding.enocean.internal.messages.ERP1Message;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.CommonTriggerEvents;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;

/**
*
lolodomo marked this conversation as resolved.
Show resolved Hide resolved
* @author Thomas Lauterbach - Initial contribution
*/
public class D2_06_01 extends _VLDMessage {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add @NonNullByDefault

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @lolodomo

I added the annotation but I'm struggling with the annotation of the getCurrentStateFunc parameter of the convertToStateImpl method. If I annotate it with Nullable it will still give me an Exception due to a mismatched annotation. I think this might be due to the fact that State is annotated as NonNull but I can't figure out how to fix that... Any help would be highly appreciated, thank you!

Copy link
Contributor

@lolodomo lolodomo Oct 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the problem is due to parent classes not being null annotated?
Did you try to annotate them too?
If this is the problem, maybe we can forget my comment as my intention was not to tell you to annotate many classes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be the case but it would go way beyond the scope of this PR to annotate large parts of the binding (no null annotations are used so far).

The correct way to do this for my single class (as the start of an iterative approach) would be as I understand it from the documentation to annotate the parameters with Nullable. This works fine for all parameter except for the Function<String, State> parameter with its generic types. What's special is that the State type is an openHAB core class that is properly annotated while the others are plain basic Java classes that are not annotated at all. I tried a lot of different things without really knowing what I'm doing and finally gave up yesterday night...

Copy link
Contributor

@lolodomo lolodomo Oct 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok so please forget this comment in the context of this PR and push your updates.


public D2_06_01() {
super();
}

public D2_06_01(ERP1Message packet) {
super(packet);
}

protected State getWindowSashState() {
int sashState = bytes[2] & 0x0F;
switch (sashState) {
case 0x01:
return new StringType("NOT TILTED");
case 0x02:
return new StringType("TILTED");
}

return UnDefType.UNDEF;
}

protected State getWindowHandleState() {
int handleState = bytes[2] >>> 4;
switch (handleState) {
case 0x01:
return new StringType("UP");
case 0x02:
return new StringType("DOWN");
case 0x03:
return new StringType("LEFT");
case 0x04:
return new StringType("RIGHT");
}

return UnDefType.UNDEF;
}

protected State getMotionState() {
int motionState = bytes[4] >>> 4;
switch (motionState) {
case 0x00:
return OnOffType.OFF;
case 0x01:
return OnOffType.ON;
}
return UnDefType.UNDEF;
}

protected State getTemperature() {
double unscaledTemp = (double) (bytes[5] & 0xFF);
if (unscaledTemp <= 250) {
double scaledTemp = unscaledTemp * 0.32 - 20;
return new QuantityType<>(scaledTemp, SIUnits.CELSIUS);
}
return UnDefType.UNDEF;
}

protected State getHumidity() {
int unscaledHumidity = bytes[6] & 0xFF;
if (unscaledHumidity <= 200) {
double scaledHumidity = unscaledHumidity * 0.5;
return new DecimalType(scaledHumidity);
}
return UnDefType.UNDEF;
}

protected State getIllumination() {
int illumination = ((bytes[7] & 0xFF) << 8) | (bytes[8] & 0xFF);
if (illumination <= 60000) {
return new QuantityType<>(illumination, Units.LUX);
}
return UnDefType.UNDEF;
}

protected State getBatteryLevel() {
int unscaledBatteryLevel = ((bytes[9] & 0xFF) >> 3);
if (unscaledBatteryLevel <= 20) {
return new DecimalType(unscaledBatteryLevel * 5);
}
return UnDefType.UNDEF;
}

@Override
protected String convertToEventImpl(String channelId, String channelTypeId, String lastEvent,
Configuration config) {

// Sensor values
if (bytes[0] == 0x00) {
fruggy83 marked this conversation as resolved.
Show resolved Hide resolved
switch (channelId) {
case CHANNEL_WINDOWBREACHEVENT:
if ((bytes[1] >>> 4) == 0x01) {
return "ALARM";
}
break;
case CHANNEL_PROTECTIONPLUSEVENT:
if ((bytes[1] & 0x0F) == 0x01) {
return "ALARM";
}
break;
case CHANNEL_PUSHBUTTON:
int buttonEvent = bytes[3] >>> 4;
switch (buttonEvent) {
case 0x01:
return CommonTriggerEvents.PRESSED;
case 0x02:
return CommonTriggerEvents.RELEASED;
}
break;
case CHANNEL_PUSHBUTTON2:
int buttonEvent2 = bytes[3] & 0x0F;
switch (buttonEvent2) {
case 0x01:
return CommonTriggerEvents.PRESSED;
case 0x02:
return CommonTriggerEvents.RELEASED;
}
break;
case CHANNEL_VACATIONMODETOGGLEEVENT:
int vacationModeToggleEvent = bytes[4] & 0x0F;
switch (vacationModeToggleEvent) {
case 0x01:
return "ACTIVATED";
case 0x02:
return "DEACTIVATED";
}
break;
}
}
return null;
}

@Override
public State convertToStateImpl(String channelId, String channelTypeId, Function<String, State> getCurrentStateFunc,
Configuration config) {

// Sensor values
if (bytes[0] == 0x00) {
switch (channelId) {
case CHANNEL_WINDOWSASHSTATE:
return getWindowSashState();
case CHANNEL_WINDOWHANDLESTATE:
return getWindowHandleState();
case CHANNEL_MOTIONDETECTION:
return getMotionState();
case CHANNEL_INDOORAIRTEMPERATURE:
return getTemperature();
case CHANNEL_HUMIDITY:
return getHumidity();
case CHANNEL_ILLUMINATION:
return getIllumination();
case CHANNEL_BATTERY_LEVEL:
return getBatteryLevel();
}
}

return UnDefType.UNDEF;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
import org.openhab.binding.enocean.internal.eep.D2_01.D2_01_12_NodON;
import org.openhab.binding.enocean.internal.eep.D2_03.D2_03_0A;
import org.openhab.binding.enocean.internal.eep.D2_05.D2_05_00;
import org.openhab.binding.enocean.internal.eep.D2_06.D2_06_01;
import org.openhab.binding.enocean.internal.eep.D2_06.D2_06_50;
import org.openhab.binding.enocean.internal.eep.D2_14.D2_14_30;
import org.openhab.binding.enocean.internal.eep.D2_50.D2_50;
Expand Down Expand Up @@ -195,6 +196,10 @@ public enum EEPType {
CHANNEL_WINDOWHANDLESTATE, CHANNEL_CONTACT, CHANNEL_BATTERY_VOLTAGE),
MechanicalHandle03(RORG._4BS, 0x14, 0x0A, false, A5_14_0A.class, THING_TYPE_MECHANICALHANDLE,
CHANNEL_WINDOWHANDLESTATE, CHANNEL_CONTACT, CHANNEL_VIBRATION, CHANNEL_BATTERY_VOLTAGE),
MechanicalHandle04(RORG.VLD, 0x06, 0x01, false, "Soda", 0x0043, D2_06_01.class, THING_TYPE_MECHANICALHANDLE,
fruggy83 marked this conversation as resolved.
Show resolved Hide resolved
CHANNEL_WINDOWHANDLESTATE, CHANNEL_WINDOWSASHSTATE, CHANNEL_MOTIONDETECTION, CHANNEL_INDOORAIRTEMPERATURE,
CHANNEL_HUMIDITY, CHANNEL_ILLUMINATION, CHANNEL_BATTERY_LEVEL, CHANNEL_WINDOWBREACHEVENT,
CHANNEL_PROTECTIONPLUSEVENT, CHANNEL_PUSHBUTTON, CHANNEL_PUSHBUTTON2, CHANNEL_VACATIONMODETOGGLEEVENT),

ContactAndSwitch01(RORG._1BS, 0x00, 0x01, false, D5_00_01.class, THING_TYPE_CONTACT, CHANNEL_CONTACT),
ContactAndSwitch02(RORG._4BS, 0x14, 0x01, false, A5_14_01.class, THING_TYPE_CONTACT, CHANNEL_BATTERY_VOLTAGE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<option value="F6_10_01">F6-10-01</option>
<option value="A5_14_09">A5-14-09</option>
<option value="A5_14_0A">A5-14-0A</option>
<option value="D2_06_01_Soda">D2_06_01 (Soda handle)</option>
</options>
<limitToOptions>true</limitToOptions>
</parameter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
</parameter>
<parameter name="receivingEEPId" type="text" required="true">
<label>EEP</label>
<description>EEP which is used by handle</description>
<description>EEP which is used by device</description>
<options>
<option value="D2_06_50_Siegenia">D2_06_50 (Siegenia Senso Secure)</option>
</options>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@
<option value="OPEN">Open</option>
<option value="TILTED">Tilted</option>
<option value="CLOSED">Closed</option>
<option value="UP">Up</option>
<option value="DOWN">Down</option>
<option value="LEFT">Left</option>
<option value="RIGHT">Right</option>
</options>
</state>
</channel-type>
Expand All @@ -166,6 +170,7 @@
<options>
<option value="OPEN">Open</option>
<option value="TILTED">Tilted</option>
<option value="NOT TILTED">Not Tilted</option>
<option value="CLOSED">Closed</option>
</options>
</state>
Expand Down Expand Up @@ -216,6 +221,29 @@
</event>
</channel-type>

<channel-type id="protectionPlusEvent">
<kind>trigger</kind>
<label>Protection Plus Event</label>
<description>Triggered when a Protection Plus capable device detects a break-in attempt.</description>
<event>
<options>
<option value="ALARM">alarm</option>
lolodomo marked this conversation as resolved.
Show resolved Hide resolved
</options>
</event>
</channel-type>

<channel-type id="vacationModeToggleEvent">
<kind>trigger</kind>
fruggy83 marked this conversation as resolved.
Show resolved Hide resolved
<label>Vacation Mode Toggle Event</label>
<description>Triggered when the vacation mode has been toggled on the device.</description>
<event>
<options>
<option value="ACTIVATED">activated</option>
<option value="DEACTIVATED">deactivated</option>
lolodomo marked this conversation as resolved.
Show resolved Hide resolved
</options>
</event>
</channel-type>

<channel-type id="instantpower">
<item-type>Number:Power</item-type>
<label>Instant Power</label>
Expand Down