From bff1810aec6650ce9a27f36c28a5b84ab49d50ee Mon Sep 17 00:00:00 2001 From: Christoph Weitkamp Date: Mon, 28 Sep 2020 21:35:37 +0200 Subject: [PATCH] [avmfritz] Added support for DECT440 device (#8588) Signed-off-by: Christoph Weitkamp --- .../org.openhab.binding.avmfritz/README.md | 108 ++++++++++++------ ...AVM_FRITZDECT_200_301_500_freigestellt.png | Bin 0 -> 37955 bytes .../internal/AVMFritzBindingConstants.java | 29 +++-- .../internal/dto/AVMFritzBaseModel.java | 5 +- .../handler/AVMFritzButtonHandler.java | 54 ++++++++- .../OH-INF/i18n/avmfritz_de.properties | 25 +++- .../resources/OH-INF/thing/channel-types.xml | 29 ++++- .../resources/OH-INF/thing/thing-types.xml | 37 +++++- .../dto/AVMFritzDeviceListModelTest.java | 69 ++++++++++- .../AVMFritzDiscoveryServiceOSGiTest.java | 3 +- 10 files changed, 295 insertions(+), 64 deletions(-) create mode 100644 bundles/org.openhab.binding.avmfritz/doc/AVM_FRITZDECT_200_301_500_freigestellt.png diff --git a/bundles/org.openhab.binding.avmfritz/README.md b/bundles/org.openhab.binding.avmfritz/README.md index a168d9bcb..94405eadd 100644 --- a/bundles/org.openhab.binding.avmfritz/README.md +++ b/bundles/org.openhab.binding.avmfritz/README.md @@ -1,6 +1,8 @@ # AVM FRITZ! Binding -The binding integrates AVM FRITZ!Boxes with a special focus on the AHA ( [AVM Home Automation](https://avm.de/ratgeber/smart-home/) ) features. +The binding integrates AVM FRITZ!Boxes with a special focus on the AHA ([AVM Home Automation](https://avm.de/ratgeber/filter/smart-home/)) features. + +![FRITZ!DECT 200 301 500](doc/AVM_FRITZDECT_200_301_500_freigestellt.png) ## Supported Things @@ -26,23 +28,34 @@ It only supports temperature readings. ### FRITZ!Powerline 546E -This [powerline adapter](https://avm.de/produkte/fritzpowerline/fritzpowerline-546e/) can be used via the bridge or in stand-alone mode. +This [powerline adapter](https://avm.de/produkte/fritzpowerline/) can be used via the bridge or in stand-alone mode. It supports switching the outlet and reading the current power, current voltage and accumulated energy consumption. This device does not contain a temperature sensor. **NOTE:** The `voltage` channel will be added to the thing during runtime - if the interface supports it (FRITZ!OS 7 or higher). ### FRITZ!DECT 301 / FRITZ!DECT 300 / Comet DECT -These devices [FRITZ!DECT 301](https://avm.de/produkte/fritzdect/fritzdect-301/), FRITZ!DECT 300 and [Comet DECT](https://www.eurotronic.org/produkte/comet-dect.html) ( [EUROtronic Technology GmbH](https://www.eurotronic.org) ) are used to regulate radiators via DECT protocol. +These devices [FRITZ!DECT 301](https://avm.de/produkte/fritzdect/fritzdect-301/), FRITZ!DECT 300 and [Comet DECT](https://eurotronic.org/produkte/dect-ule-heizkoerperthermostat/comet-dect/) ([EUROtronic Technology GmbH](https://eurotronic.org/)) are used to regulate radiators via DECT-ULE protocol. The FRITZ!Box can handle up to twelve heating thermostats. The binding provides channels for reading and setting the temperature. Additionally you can check the eco temperature, the comfort temperature and the battery level of the device. The FRITZ!Box has to run at least on firmware FRITZ!OS 6.35. **NOTE:** The `battery_level` channel will be added to the thing during runtime - if the interface supports it (FRITZ!OS 7 or higher). -### FRITZ!DECT 400 +### FRITZ!DECT 400 / FRITZ!DECT 440 -The [FRITZ!DECT 400](https://avm.de/produkte/fritzdect/fritzdect-400/) is a button for convenient operation of FRITZ! Smart Home devices (FRITZ!OS 7.08 or higher). +The [FRITZ!DECT 400](https://avm.de/produkte/fritzdect/fritzdect-400/) and [FRITZ!DECT 440](https://avm.de/produkte/fritzdect/fritzdect-440/) are buttons for convenient operation of FRITZ! Smart Home devices (FRITZ!OS 7.08 or higher for FRITZ!DECT 400, 7.20 or higher for FRITZ!DECT 440). +The FRITZ!DECT 400 supports a configurable button to trigger short or long press events. +Beside four customizable buttons the FRITZ!DECT 440 supports temperature readings. +** NOTE: ** FRITZ!DECT 440 now uses Channel Groups to group its Channels like `device#battery_level`, `device#battery_low` for device information, `sensors#temperature` for sensor data and `top-left`, `bottom-left`, `top-right` and `bottom-right` combined with `press` and `last_change` (see [Full Example](#full-example)) + +#### Supported Channel Groups + +| Channel Group ID | Description | Available on thing | +|--------------------------------------------------------|---------------------|--------------------| +| `device` | Device information. | FRITZ!DECT 440 | +| `sensors` | Sensor data. | FRITZ!DECT 440 | +| `top-left`, `bottom-left`, `top-right`, `bottom-right` | Button and trigger. | FRITZ!DECT 440 | ### DECT-ULE / HAN-FUN Devices @@ -80,7 +93,7 @@ To do so - Enable the option "Login with FRITZ!Box user name and password". - Click "Apply" to save the settings. -Note: Now you can only log in to the FRITZ!Box with a user account, i.e. after entering a user name and password. +**NOTE:** Now you can only log in to the FRITZ!Box with a user account, i.e. after entering a user name and password. Auto-discovery is enabled by default. To disable it, you can add the following line to `/services/runtime.cfg`: @@ -121,47 +134,48 @@ If the FRITZ!Powerline 546E is added via auto-discovery it determines its own `a - `ain` (mandatory), no default (AIN number of the device) -### Finding The AIN ### +### Finding The AIN The AIN (actor identification number) can be found in the FRITZ!Box interface -> Home Network -> SmartHome. When opening the details view for a device with the edit button, the AIN is shown. Use the AIN without the blank. ## Supported Channels -| Channel Type ID | Item Type | Description | Available on thing | -|-----------------|--------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------| -| incoming_call | Call | Details about incoming call. | FRITZ!Box | -| outgoing_call | Call | Details about outgoing call. | FRITZ!Box | -| active_call | Call | Details about active call. | FRITZ!Box | -| call_state | String | Details about current call state, either IDLE, RINGING, DIALING or ACTIVE. | FRITZ!Box | -| apply_template | String | Apply template for device(s) (channel's state options contains available templates, for an alternative way see the description below) - FRITZ!OS 7 | FRITZ!Box, FRITZ!Powerline 546E | -| mode | String | States the mode of the device (MANUAL/AUTOMATIC/VACATION) | FRITZ!DECT 210, FRITZ!DECT 200, FRITZ!Powerline 546E, FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT | -| locked | Contact | Device is locked for switching over external sources (OPEN/CLOSE) | FRITZ!DECT 210, FRITZ!DECT 200, FRITZ!Powerline 546E, FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT | -| device_locked | Contact | Device is locked for switching manually (OPEN/CLOSE) - FRITZ!OS 6.90 | FRITZ!DECT 210, FRITZ!DECT 200, FRITZ!Powerline 546E, FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT | -| temperature | Number:Temperature | Current measured temperature | FRITZ!DECT 210, FRITZ!DECT 200, FRITZ!DECT Repeater 100, FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT | -| energy | Number:Energy | Accumulated energy consumption | FRITZ!DECT 210, FRITZ!DECT 200, FRITZ!Powerline 546E | -| power | Number:Power | Current power consumption | FRITZ!DECT 210, FRITZ!DECT 200, FRITZ!Powerline 546E | -| voltage | Number:ElectricPotential | Current voltage - FRITZ!OS 7 | FRITZ!DECT 210, FRITZ!DECT 200, FRITZ!Powerline 546E | -| outlet | Switch | Switchable outlet (ON/OFF) | FRITZ!DECT 210, FRITZ!DECT 200, FRITZ!Powerline 546E | -| actual_temp | Number:Temperature | Current temperature of heating thermostat | FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT | -| set_temp | Number:Temperature | Set Temperature of heating thermostat | FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT | -| eco_temp | Number:Temperature | Eco Temperature of heating thermostat | FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT | -| comfort_temp | Number:Temperature | Comfort Temperature of heating thermostat | FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT | -| radiator_mode | String | Mode of heating thermostat (ON/OFF/COMFORT/ECO/BOOST/WINDOW_OPEN) | FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT | -| next_change | DateTime | Next change of the Set Temperature if scheduler is activated in the FRITZ!Box settings - FRITZ!OS 6.80 | FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT | -| next_temp | Number:Temperature | Next Set Temperature if scheduler is activated in the FRITZ!Box settings - FRITZ!OS 6.80 | FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT | -| battery_level | Number | Battery level (in %) - FRITZ!OS 7 | FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT, FRITZ!DECT 400 | -| battery_low | Switch | Battery level low (ON/OFF) - FRITZ!OS 6.80 | FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT, FRITZ!DECT 400 | -| contact_state | Contact | Contact state information (OPEN/CLOSED). | HAN-FUN contact (e.g. SmartHome Tür-/Fensterkontakt or SmartHome Bewegungsmelder)- FRITZ!OS 7 | -| last_change | DateTime | States the last time the button was pressed. | FRITZ!DECT 400, HAN-FUN switch (e.g. SmartHome Wandtaster) - FRITZ!OS 7 | +| Channel Type ID | Item Type | Description | Available on thing | +|-----------------|--------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------| +| incoming_call | Call | Details about incoming call. | FRITZ!Box | +| outgoing_call | Call | Details about outgoing call. | FRITZ!Box | +| active_call | Call | Details about active call. | FRITZ!Box | +| call_state | String | Details about current call state, either IDLE, RINGING, DIALING or ACTIVE. | FRITZ!Box | +| apply_template | String | Apply template for device(s) (channel's state options contains available templates, for an alternative way see the description below) - FRITZ!OS 7 | FRITZ!Box, FRITZ!Powerline 546E | +| mode | String | States the mode of the device (MANUAL/AUTOMATIC/VACATION) | FRITZ!DECT 210, FRITZ!DECT 200, FRITZ!Powerline 546E, FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT | +| locked | Contact | Device is locked for switching over external sources (OPEN/CLOSE) | FRITZ!DECT 210, FRITZ!DECT 200, FRITZ!Powerline 546E, FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT | +| device_locked | Contact | Device is locked for switching manually (OPEN/CLOSE) - FRITZ!OS 6.90 | FRITZ!DECT 210, FRITZ!DECT 200, FRITZ!Powerline 546E, FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT | +| temperature | Number:Temperature | Current measured temperature | FRITZ!DECT 210, FRITZ!DECT 200, FRITZ!DECT Repeater 100, FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT, FRITZ!DECT 440 | +| energy | Number:Energy | Accumulated energy consumption | FRITZ!DECT 210, FRITZ!DECT 200, FRITZ!Powerline 546E | +| power | Number:Power | Current power consumption | FRITZ!DECT 210, FRITZ!DECT 200, FRITZ!Powerline 546E | +| voltage | Number:ElectricPotential | Current voltage - FRITZ!OS 7 | FRITZ!DECT 210, FRITZ!DECT 200, FRITZ!Powerline 546E | +| outlet | Switch | Switchable outlet (ON/OFF) | FRITZ!DECT 210, FRITZ!DECT 200, FRITZ!Powerline 546E | +| actual_temp | Number:Temperature | Current temperature of heating thermostat | FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT | +| set_temp | Number:Temperature | Set Temperature of heating thermostat | FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT | +| eco_temp | Number:Temperature | Eco Temperature of heating thermostat | FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT | +| comfort_temp | Number:Temperature | Comfort Temperature of heating thermostat | FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT | +| radiator_mode | String | Mode of heating thermostat (ON/OFF/COMFORT/ECO/BOOST/WINDOW_OPEN) | FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT | +| next_change | DateTime | Next change of the Set Temperature if scheduler is activated in the FRITZ!Box settings - FRITZ!OS 6.80 | FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT | +| next_temp | Number:Temperature | Next Set Temperature if scheduler is activated in the FRITZ!Box settings - FRITZ!OS 6.80 | FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT | +| battery_level | Number | Battery level (in %) - FRITZ!OS 7 | FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT, FRITZ!DECT 400, FRITZ!DECT 440 | +| battery_low | Switch | Battery level low (ON/OFF) - FRITZ!OS 6.80 | FRITZ!DECT 301, FRITZ!DECT 300, Comet DECT, FRITZ!DECT 400, FRITZ!DECT 440 | +| contact_state | Contact | Contact state information (OPEN/CLOSED). | HAN-FUN contact (e.g. SmartHome Tür-/Fensterkontakt or SmartHome Bewegungsmelder)- FRITZ!OS 7 | +| last_change | DateTime | States the last time the button was pressed. | FRITZ!DECT 400, FRITZ!DECT 440, HAN-FUN switch (e.g. SmartHome Wandtaster) - FRITZ!OS 7 | ### Triggers -| Channel Type ID | Item Type | Description | Available on thing | -|-----------------|-----------|--------------------------------------------------------------------------------|---------------------------------------------------------| -| press | Trigger | Dispatches a `PRESSED` event when a button is pressed. | HAN-FUN switch (e.g. SmartHome Wandtaster) - FRITZ!OS 7 | -| press | Trigger | Dispatches a `SHORT_PRESSED` or `LONG_PRESSED` event when a button is pressed. | FRITZ!DECT 400 - FRITZ!OS 7.08 | +| Channel Type ID | Item Type | Description | Available on thing | +|-----------------|-----------|--------------------------------------------------------------------------------|-----------------------------------------------------------| +| press | Trigger | Dispatches a `PRESSED` event when a button is pressed. | FRITZ!DECT440, HAN-FUN switch (e.g. SmartHome Wandtaster) | +| press | Trigger | Dispatches a `SHORT_PRESSED` or `LONG_PRESSED` event when a button is pressed. | FRITZ!DECT 400 | -The trigger channel `press` is of type `system.rawbutton` to allow the usage of the `rawbutton-toggle-switch` profile. +The trigger channel `press` for a FRITZ!DECT 440 device or HAN-FUN switch is of type `system.rawbutton` to allow the usage of the `rawbutton-toggle-switch` profile. +The trigger channel `press` for a FRITZ!DECT 400 device is of type `system.button`. ### FRITZ! Smart Home Templates @@ -210,6 +224,7 @@ demo.things: ```java Bridge avmfritz:fritzbox:1 "FRITZ!Box" [ ipAddress="192.168.x.x", password="xxx", user="xxx" ] { + Thing FRITZ_DECT_440 vvvvvvvvvvvv "FRITZ!DECT 440 #15" [ ain="vvvvvvvvvvvv" ] Thing FRITZ_DECT_200 xxxxxxxxxxxx "FRITZ!DECT 200 #1" [ ain="xxxxxxxxxxxx" ] Thing FRITZ_Powerline_546E yy_yy_yy_yy_yy_yy "FRITZ!Powerline 546E #2" [ ain="yy:yy:yy:yy:yy:yy" ] Thing Comet_DECT aaaaaabbbbbb "Comet DECT #3" [ ain="aaaaaabbbbbb" ] @@ -230,6 +245,14 @@ Call OutgoingCall "Outgoing call: [%1$s to %2$s]" { channel="avmfritz:fritzbo Call ActiveCall "Call established [%1$s]" { channel="avmfritz:fritzbox:1:active_call" } String ApplyTemplate "Apply template" { channel="avmfritz:fritzbox:1:apply_template" } +Number:Temperature SwitchTemperature "Current measured temperature [%.1f %unit%]" { channel="avmfritz:FRITZ_DECT_440:1:vvvvvvvvvvvv:sensors#temperature" } +Number SwitchBatteryLevel "Battery level" { channel="avmfritz:FRITZ_DECT_440:1:vvvvvvvvvvvv:device#battery_level" } +Switch SwitchBatteryLow "Battery low" { channel="avmfritz:FRITZ_DECT_440:1:vvvvvvvvvvvv:device#battery_low" } +DateTime TopLeftSwitchLastChanged "Last change" { channel="avmfritz:FRITZ_DECT_440:1:vvvvvvvvvvvv:top-left#last_change" } +DateTime BottomLeftSwitchLastChanged "Last change" { channel="avmfritz:FRITZ_DECT_440:1:vvvvvvvvvvvv:bottom-left#last_change" } +DateTime TopRightSwitchLastChanged "Last change" { channel="avmfritz:FRITZ_DECT_440:1:vvvvvvvvvvvv:top-right#last_change" } +DateTime BottomRightSwitchLastChanged "Last change" { channel="avmfritz:FRITZ_DECT_440:1:vvvvvvvvvvvv:bottom-right#last_change" } + Switch Outlet1 "Switchable outlet" { channel="avmfritz:FRITZ_DECT_200:1:xxxxxxxxxxxx:outlet" } Number:Temperature Temperature1 "Current measured temperature [%.1f %unit%]" { channel="avmfritz:FRITZ_DECT_200:1:xxxxxxxxxxxx:temperature" } Number:Energy Energy1 "Accumulated energy consumption [%.3f kWh]" { channel="avmfritz:FRITZ_DECT_200:1:xxxxxxxxxxxx:energy" } @@ -300,6 +323,15 @@ sitemap demo label="Main Menu" { demo.rules: +```java +rule "FRITZ!DECT 440 Top Left Button pressed" +when + Channel "avmfritz:FRITZ_DECT_440:1:vvvvvvvvvvvv:top-left#press" triggered PRESSED +then + logInfo("demo", "Top Left Button pressed") +end +``` + ```java rule "HAN-FUN Button pressed" when diff --git a/bundles/org.openhab.binding.avmfritz/doc/AVM_FRITZDECT_200_301_500_freigestellt.png b/bundles/org.openhab.binding.avmfritz/doc/AVM_FRITZDECT_200_301_500_freigestellt.png new file mode 100644 index 0000000000000000000000000000000000000000..2cbaa23c85b0b4e1ace9097f9648732236c225aa GIT binary patch literal 37955 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 U+PwFF6%cSC^^NtK5Du~b2e}iFX8-^I literal 0 HcmV?d00001 diff --git a/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/AVMFritzBindingConstants.java b/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/AVMFritzBindingConstants.java index f3328affe..0589c5e37 100644 --- a/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/AVMFritzBindingConstants.java +++ b/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/AVMFritzBindingConstants.java @@ -41,6 +41,7 @@ public class AVMFritzBindingConstants { // List of main device types public static final String DEVICE_DECT400 = "FRITZ_DECT_400"; + public static final String DEVICE_DECT440 = "FRITZ_DECT_440"; public static final String DEVICE_DECT301 = "FRITZ_DECT_301"; public static final String DEVICE_DECT300 = "FRITZ_DECT_300"; public static final String DEVICE_DECT210 = "FRITZ_DECT_210"; @@ -59,6 +60,7 @@ public class AVMFritzBindingConstants { // List of all Thing Type UIDs public static final ThingTypeUID BRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, BRIDGE_FRITZBOX); public static final ThingTypeUID DECT400_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_DECT400); + public static final ThingTypeUID DECT440_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_DECT440); public static final ThingTypeUID DECT301_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_DECT301); public static final ThingTypeUID DECT300_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_DECT300); public static final ThingTypeUID DECT210_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_DECT210); @@ -86,6 +88,12 @@ public class AVMFritzBindingConstants { public static final String PROPERTY_MASTER = "master"; public static final String PROPERTY_MEMBERS = "members"; + // List of all channel groups + public static final String CHANNEL_GROUP_TOP_LEFT = "top-left"; + public static final String CHANNEL_GROUP_BOTTOM_LEFT = "bottom-left"; + public static final String CHANNEL_GROUP_TOP_RIGHT = "top-right"; + public static final String CHANNEL_GROUP_BOTTOM_RIGHT = "bottom-right"; + // List of all Channel ids public static final String CHANNEL_CALL_INCOMING = "incoming_call"; public static final String CHANNEL_CALL_OUTGOING = "outgoing_call"; @@ -145,21 +153,20 @@ public class AVMFritzBindingConstants { public static final String MODE_WINDOW_OPEN = "WINDOW_OPEN"; public static final String MODE_UNKNOWN = "UNKNOWN"; - public static final Set SUPPORTED_BUTTON_THING_TYPES_UIDS = Collections - .unmodifiableSet(Stream.of(DECT400_THING_TYPE, HAN_FUN_SWITCH_THING_TYPE).collect(Collectors.toSet())); + public static final Set SUPPORTED_BUTTON_THING_TYPES_UIDS = Set.of(DECT400_THING_TYPE, + DECT440_THING_TYPE, HAN_FUN_SWITCH_THING_TYPE); - public static final Set SUPPORTED_HEATING_THING_TYPES = Collections.unmodifiableSet( - Stream.of(DECT300_THING_TYPE, DECT301_THING_TYPE, COMETDECT_THING_TYPE).collect(Collectors.toSet())); + public static final Set SUPPORTED_HEATING_THING_TYPES = Set.of(DECT300_THING_TYPE, DECT301_THING_TYPE, + COMETDECT_THING_TYPE); - public static final Set SUPPORTED_DEVICE_THING_TYPES_UIDS = Collections - .unmodifiableSet(Stream.of(DECT100_THING_TYPE, DECT200_THING_TYPE, DECT210_THING_TYPE, PL546E_THING_TYPE, - HAN_FUN_CONTACT_THING_TYPE).collect(Collectors.toSet())); + public static final Set SUPPORTED_DEVICE_THING_TYPES_UIDS = Set.of(DECT100_THING_TYPE, + DECT200_THING_TYPE, DECT210_THING_TYPE, PL546E_THING_TYPE, HAN_FUN_CONTACT_THING_TYPE); - public static final Set SUPPORTED_GROUP_THING_TYPES_UIDS = Collections - .unmodifiableSet(Stream.of(GROUP_HEATING_THING_TYPE, GROUP_SWITCH_THING_TYPE).collect(Collectors.toSet())); + public static final Set SUPPORTED_GROUP_THING_TYPES_UIDS = Set.of(GROUP_HEATING_THING_TYPE, + GROUP_SWITCH_THING_TYPE); - public static final Set SUPPORTED_BRIDGE_THING_TYPES_UIDS = Collections - .unmodifiableSet(Stream.of(BRIDGE_THING_TYPE, PL546E_STANDALONE_THING_TYPE).collect(Collectors.toSet())); + public static final Set SUPPORTED_BRIDGE_THING_TYPES_UIDS = Set.of(BRIDGE_THING_TYPE, + PL546E_STANDALONE_THING_TYPE); public static final Set SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(Stream .of(SUPPORTED_BUTTON_THING_TYPES_UIDS, SUPPORTED_HEATING_THING_TYPES, SUPPORTED_DEVICE_THING_TYPES_UIDS, diff --git a/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/dto/AVMFritzBaseModel.java b/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/dto/AVMFritzBaseModel.java index 6093ed584..0dbe19773 100644 --- a/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/dto/AVMFritzBaseModel.java +++ b/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/dto/AVMFritzBaseModel.java @@ -24,8 +24,9 @@ import javax.xml.bind.annotation.XmlElement; * *
    *
  1. Bit 0: HAN-FUN Gerät
  2. - *
  3. Bit 3: Button
  4. + *
  5. Bit 3: HAN-FUN Button - undocumented
  6. *
  7. Bit 4: Alarm-Sensor
  8. + *
  9. Bit 5: AVM-Button
  10. *
  11. Bit 6: Comet DECT, Heizkörperregler
  12. *
  13. Bit 7: Energie Messgerät
  14. *
  15. Bit 8: Temperatursensor
  16. @@ -43,7 +44,7 @@ public abstract class AVMFritzBaseModel implements BatteryModel { protected static final int HAN_FUN_DEVICE_BIT = 1; // Bit 0 protected static final int HAN_FUN_BUTTON_BIT = 1 << 3; // Bit 3 - undocumented protected static final int HAN_FUN_ALARM_SENSOR_BIT = 1 << 4; // Bit 4 - protected static final int BUTTON_BIT = 1 << 5; // Bit 5 - undocumented + protected static final int BUTTON_BIT = 1 << 5; // Bit 5 protected static final int HEATING_THERMOSTAT_BIT = 1 << 6; // Bit 6 protected static final int POWERMETER_BIT = 1 << 7; // Bit 7 protected static final int TEMPSENSOR_BIT = 1 << 8; // Bit 8 diff --git a/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/handler/AVMFritzButtonHandler.java b/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/handler/AVMFritzButtonHandler.java index e59f2edcd..d8d681794 100644 --- a/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/handler/AVMFritzButtonHandler.java +++ b/bundles/org.openhab.binding.avmfritz/src/main/java/org/openhab/binding/avmfritz/internal/handler/AVMFritzButtonHandler.java @@ -18,13 +18,16 @@ import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; +import java.util.Optional; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.avmfritz.internal.dto.AVMFritzBaseModel; import org.openhab.binding.avmfritz.internal.dto.ButtonModel; import org.openhab.binding.avmfritz.internal.dto.DeviceModel; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.CommonTriggerEvents; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingUID; @@ -40,6 +43,11 @@ import org.slf4j.LoggerFactory; @NonNullByDefault public class AVMFritzButtonHandler extends DeviceHandler { + private static final String TOP_RIGHT_SUFFIX = "-1"; + private static final String BOTTOM_RIGHT_SUFFIX = "-3"; + private static final String BOTTOM_LEFT_SUFFIX = "-5"; + private static final String TOP_LEFT_SUFFIX = "-7"; + private final Logger logger = LoggerFactory.getLogger(AVMFritzButtonHandler.class); /** * keeps track of the last timestamp for handling trigger events @@ -67,7 +75,11 @@ public class AVMFritzButtonHandler extends DeviceHandler { updateHANFUNButton(deviceModel.getButtons()); } if (deviceModel.isButton()) { - updateShortLongPressButton(deviceModel.getButtons()); + if (DECT400_THING_TYPE.equals(thing.getThingTypeUID())) { + updateShortLongPressButton(deviceModel.getButtons()); + } else if (DECT440_THING_TYPE.equals(thing.getThingTypeUID())) { + updateButtons(deviceModel.getButtons()); + } updateBattery(deviceModel); } } @@ -88,6 +100,29 @@ public class AVMFritzButtonHandler extends DeviceHandler { } } + private void updateButtons(List buttons) { + Optional topLeft = buttons.stream().filter(b -> b.getIdentifier().endsWith(TOP_LEFT_SUFFIX)) + .findFirst(); + if (topLeft.isPresent()) { + updateButton(topLeft.get(), CommonTriggerEvents.PRESSED, CHANNEL_GROUP_TOP_LEFT); + } + Optional bottomLeft = buttons.stream().filter(b -> b.getIdentifier().endsWith(BOTTOM_LEFT_SUFFIX)) + .findFirst(); + if (bottomLeft.isPresent()) { + updateButton(bottomLeft.get(), CommonTriggerEvents.PRESSED, CHANNEL_GROUP_BOTTOM_LEFT); + } + Optional topRight = buttons.stream().filter(b -> b.getIdentifier().endsWith(TOP_RIGHT_SUFFIX)) + .findFirst(); + if (topRight.isPresent()) { + updateButton(topRight.get(), CommonTriggerEvents.PRESSED, CHANNEL_GROUP_TOP_RIGHT); + } + Optional bottomRight = buttons.stream() + .filter(b -> b.getIdentifier().endsWith(BOTTOM_RIGHT_SUFFIX)).findFirst(); + if (bottomRight.isPresent()) { + updateButton(bottomRight.get(), CommonTriggerEvents.PRESSED, CHANNEL_GROUP_BOTTOM_RIGHT); + } + } + private void updateHANFUNButton(List buttons) { if (!buttons.isEmpty()) { updateButton(buttons.get(0), CommonTriggerEvents.PRESSED); @@ -95,9 +130,16 @@ public class AVMFritzButtonHandler extends DeviceHandler { } private void updateButton(ButtonModel buttonModel, String event) { + updateButton(buttonModel, event, null); + } + + private void updateButton(ButtonModel buttonModel, String event, @Nullable String channelGroupId) { int lastPressedTimestamp = buttonModel.getLastpressedtimestamp(); if (lastPressedTimestamp == 0) { - updateThingChannelState(CHANNEL_LAST_CHANGE, UnDefType.UNDEF); + updateThingChannelState( + channelGroupId == null ? CHANNEL_LAST_CHANGE + : channelGroupId + ChannelUID.CHANNEL_GROUP_SEPARATOR + CHANNEL_LAST_CHANGE, + UnDefType.UNDEF); } else { ZonedDateTime timestamp = ZonedDateTime.ofInstant(Instant.ofEpochSecond(lastPressedTimestamp), ZoneId.systemDefault()); @@ -106,9 +148,13 @@ public class AVMFritzButtonHandler extends DeviceHandler { // restart) if (then.isAfter(lastTimestamp)) { lastTimestamp = then; - triggerThingChannel(CHANNEL_PRESS, event); + triggerThingChannel(channelGroupId == null ? CHANNEL_PRESS + : channelGroupId + ChannelUID.CHANNEL_GROUP_SEPARATOR + CHANNEL_PRESS, event); } - updateThingChannelState(CHANNEL_LAST_CHANGE, new DateTimeType(timestamp)); + updateThingChannelState( + channelGroupId == null ? CHANNEL_LAST_CHANGE + : channelGroupId + ChannelUID.CHANNEL_GROUP_SEPARATOR + CHANNEL_LAST_CHANGE, + new DateTimeType(timestamp)); } } diff --git a/bundles/org.openhab.binding.avmfritz/src/main/resources/OH-INF/i18n/avmfritz_de.properties b/bundles/org.openhab.binding.avmfritz/src/main/resources/OH-INF/i18n/avmfritz_de.properties index 3da3e2133..a006fc748 100644 --- a/bundles/org.openhab.binding.avmfritz/src/main/resources/OH-INF/i18n/avmfritz_de.properties +++ b/bundles/org.openhab.binding.avmfritz/src/main/resources/OH-INF/i18n/avmfritz_de.properties @@ -95,6 +95,8 @@ thing-type.avmfritz.Comet_DECT.description = Comet DECT Heizk thing-type.avmfritz.FRITZ_DECT_400.description = FRITZ!DECT 400 Taster. Dient zur komfortablen Bedienung von FRITZ! Smart-Home-Geräten. +thing-type.avmfritz.FRITZ_DECT_440.description = FRITZ!DECT 440 Taster. Dient zur komfortablen Bedienung von FRITZ! Smart-Home-Geräten und liefert Daten wie z.B. Temperatur. + thing-type.avmfritz.FRITZ_Powerline_546E.description = FRITZ!Powerline 546E schaltbare Steckdose. Dient zur Steuerung der integrierten Steckdose und liefert Daten wie z.B. Temperatur. # thing types config groups @@ -109,8 +111,23 @@ thing-type.config.avmfritz.fritzdevice.ain.description = Die AHA ID (AIN) zur Id thing-type.config.avmfritz.fritzgroup.ain.description = Die AHA ID (AIN) zur Identifikation der FRITZ! Gruppe. -# channel types +# channel group types +channel-group-type.avmfritz.button.label = Taste +channel-group-type.avmfritz.device.label = Geräteinformationen + +channel-group-type.avmfritz.sensors.label = Sensordaten + +# channel groups +thing-type.avmfritz.FRITZ_DECT_440.group.top-left.label = Taste Oben Links + +thing-type.avmfritz.FRITZ_DECT_440.group.top-right.label = Taste Oben Rechts + +thing-type.avmfritz.FRITZ_DECT_440.group.bottom-left.label = Taste Unten Links + +thing-type.avmfritz.FRITZ_DECT_440.group.bottom-right.label = Taste Unten Rechts + +# channel types channel-type.avmfritz.incoming_call.label = Eingehender Anruf channel-type.avmfritz.incoming_call.description = Informationen zu anrufender und angerufener Telefonnummer. channel-type.avmfritz.outgoing_call.label = Ausgehender Anruf @@ -188,6 +205,12 @@ channel-type.avmfritz.contact_state.description = Zeigt an, ob die T thing-type.avmfritz.HAN_FUN_SWITCH.channel.press.label = Tastendruck thing-type.avmfritz.HAN_FUN_SWITCH.channel.press.description = Wird ausgelöst, wenn eine Taste gedrückt wird. +thing-type.avmfritz.FRITZ_DECT_400.channel.press.label = Tastendruck +thing-type.avmfritz.FRITZ_DECT_400.channel.press.description = Wird ausgelöst, wenn eine Taste gedrückt wird. + +thing-type.avmfritz.FRITZ_DECT_440.channel.press.label = Tastendruck +thing-type.avmfritz.FRITZ_DECT_440.channel.press.description = Wird ausgelöst, wenn eine Taste gedrückt wird. + channel-type.avmfritz.last_change.label = Letzte Änderung channel-type.avmfritz.last_change.description = Zeigt an, wann der Schalter zuletzt gedrückt wurde. channel-type.avmfritz.last_change.pattern = %1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS diff --git a/bundles/org.openhab.binding.avmfritz/src/main/resources/OH-INF/thing/channel-types.xml b/bundles/org.openhab.binding.avmfritz/src/main/resources/OH-INF/thing/channel-types.xml index e2ec3e141..410d7ca60 100644 --- a/bundles/org.openhab.binding.avmfritz/src/main/resources/OH-INF/thing/channel-types.xml +++ b/bundles/org.openhab.binding.avmfritz/src/main/resources/OH-INF/thing/channel-types.xml @@ -73,7 +73,34 @@ - + + + + + + + Triggeres PRESSED when a button was pressed. + + + + + + + + + + + + + + + + + + + + + Number:Temperature diff --git a/bundles/org.openhab.binding.avmfritz/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.avmfritz/src/main/resources/OH-INF/thing/thing-types.xml index 58c640c50..3921f4948 100644 --- a/bundles/org.openhab.binding.avmfritz/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.avmfritz/src/main/resources/OH-INF/thing/thing-types.xml @@ -16,8 +16,8 @@ - - Triggered SHORT_PRESSED or LONG_PRESSED when a button was pressed. + + Triggeres SHORT_PRESSED or LONG_PRESSED when a button was pressed. @@ -29,6 +29,37 @@ + + + + + + + + FRITZ!DECT440 switch. + + + + + + + + + + + + + + + + + + + ain + + + + @@ -236,7 +267,7 @@ - Triggered when a button was pressed. + Triggeres PRESSED when a button was pressed. diff --git a/bundles/org.openhab.binding.avmfritz/src/test/java/org/openhab/binding/avmfritz/internal/dto/AVMFritzDeviceListModelTest.java b/bundles/org.openhab.binding.avmfritz/src/test/java/org/openhab/binding/avmfritz/internal/dto/AVMFritzDeviceListModelTest.java index b5ce63c9f..045d7ec9e 100644 --- a/bundles/org.openhab.binding.avmfritz/src/test/java/org/openhab/binding/avmfritz/internal/dto/AVMFritzDeviceListModelTest.java +++ b/bundles/org.openhab.binding.avmfritz/src/test/java/org/openhab/binding/avmfritz/internal/dto/AVMFritzDeviceListModelTest.java @@ -44,7 +44,7 @@ public class AVMFritzDeviceListModelTest { @BeforeEach public void setUp() { //@formatter:off - String xml = + final String xml = "" + "1Schlafzimmer1manuell00230051020871717,18" + "1Schlafzimmer220-104442284211000000100148434120028020,21,22" + @@ -58,9 +58,9 @@ public class AVMFritzDeviceListModelTest { "0HAN-FUN #2: Unit #24065142561" + "0HAN-FUN #2: Unit #2412273772" + "1FRITZ!DECT 400 #141000" + + "1FRITZ!DECT 440 #1523001000" + ""; //@formatter:off - try { Unmarshaller u = JAXBUtils.JAXBCONTEXT_DEVICES.createUnmarshaller(); devices = (DeviceListModel) u.unmarshal(new StringReader(xml)); @@ -72,7 +72,7 @@ public class AVMFritzDeviceListModelTest { @Test public void validateDeviceListModel() { assertNotNull(devices); - assertEquals(12, devices.getDevicelist().size()); + assertEquals(13, devices.getDevicelist().size()); assertEquals("1", devices.getXmlApiVersion()); } @@ -345,6 +345,69 @@ public class AVMFritzDeviceListModelTest { assertNull(device.getHkr()); } + @Test + public void validateDECT440Model() { + Optional optionalDevice = findModelByIdentifier("130960007308"); + assertTrue(optionalDevice.isPresent()); + assertTrue(optionalDevice.get() instanceof DeviceModel); + + DeviceModel device = (DeviceModel) optionalDevice.get(); + assertEquals("FRITZ!DECT 440", device.getProductName()); + assertEquals("130960007308", device.getIdentifier()); + assertEquals("30", device.getDeviceId()); + assertEquals("04.90", device.getFirmwareVersion()); + assertEquals("AVM", device.getManufacturer()); + + assertEquals(1, device.getPresent()); + assertEquals("FRITZ!DECT 440 #15", device.getName()); + + assertTrue(device.isButton()); + assertFalse(device.isHANFUNButton()); + assertFalse(device.isHANFUNAlarmSensor()); + assertFalse(device.isDectRepeater()); + assertFalse(device.isSwitchableOutlet()); + assertTrue(device.isTempSensor()); + assertFalse(device.isPowermeter()); + assertFalse(device.isHeatingThermostat()); + + assertEquals(new BigDecimal("100"), device.getBattery()); + assertEquals(BatteryModel.BATTERY_OFF, device.getBatterylow()); + + assertEquals(4, device.getButtons().size()); + final ButtonModel topRight = device.getButtons().get(0); + assertEquals("130960007308-1", topRight.getIdentifier()); + assertEquals("5000", topRight.getButtonId()); + assertEquals("FRITZ!DECT 440 #15: Oben rechts", topRight.getName()); + assertEquals(1549195586, topRight.getLastpressedtimestamp()); + final ButtonModel bottomRight = device.getButtons().get(1); + assertEquals("130960007308-3", bottomRight.getIdentifier()); + assertEquals("5001", bottomRight.getButtonId()); + assertEquals("FRITZ!DECT 440 #15: Unten rechts", bottomRight.getName()); + assertEquals(1549195595, bottomRight.getLastpressedtimestamp()); + final ButtonModel bottomLeft = device.getButtons().get(2); + assertEquals("130960007308-5", bottomLeft.getIdentifier()); + assertEquals("5002", bottomLeft.getButtonId()); + assertEquals("FRITZ!DECT 440 #15: Unten links", bottomLeft.getName()); + assertEquals(1549195586, bottomLeft.getLastpressedtimestamp()); + final ButtonModel topLeft = device.getButtons().get(3); + assertEquals("130960007308-7", topLeft.getIdentifier()); + assertEquals("5003", topLeft.getButtonId()); + assertEquals("FRITZ!DECT 440 #15: Oben links", topLeft.getName()); + assertEquals(1549195595, topLeft.getLastpressedtimestamp()); + + assertNull(device.getAlert()); + + assertNull(device.getSwitch()); + + assertNotNull(device.getTemperature()); + assertEquals(new BigDecimal("23.0"), device.getTemperature().getCelsius()); + assertEquals(new BigDecimal("0.0"), device.getTemperature().getOffset()); + + assertNull(device.getPowermeter()); + + assertNull(device.getHkr()); + } + @Test public void validatePowerline546EModel() { Optional optionalDevice = findModel("FRITZ!Powerline 546E"); diff --git a/itests/org.openhab.binding.avmfritz.tests/src/main/java/org/openhab/binding/avmfritz/internal/discovery/AVMFritzDiscoveryServiceOSGiTest.java b/itests/org.openhab.binding.avmfritz.tests/src/main/java/org/openhab/binding/avmfritz/internal/discovery/AVMFritzDiscoveryServiceOSGiTest.java index c6d988714..13aae28eb 100644 --- a/itests/org.openhab.binding.avmfritz.tests/src/main/java/org/openhab/binding/avmfritz/internal/discovery/AVMFritzDiscoveryServiceOSGiTest.java +++ b/itests/org.openhab.binding.avmfritz.tests/src/main/java/org/openhab/binding/avmfritz/internal/discovery/AVMFritzDiscoveryServiceOSGiTest.java @@ -86,13 +86,14 @@ public class AVMFritzDiscoveryServiceOSGiTest extends AVMFritzThingHandlerOSGiTe @Test public void correctSupportedTypes() { - assertEquals(12, discovery.getSupportedThingTypes().size()); + assertEquals(13, discovery.getSupportedThingTypes().size()); assertTrue(discovery.getSupportedThingTypes().contains(DECT100_THING_TYPE)); assertTrue(discovery.getSupportedThingTypes().contains(DECT200_THING_TYPE)); assertTrue(discovery.getSupportedThingTypes().contains(DECT210_THING_TYPE)); assertTrue(discovery.getSupportedThingTypes().contains(DECT300_THING_TYPE)); assertTrue(discovery.getSupportedThingTypes().contains(DECT301_THING_TYPE)); assertTrue(discovery.getSupportedThingTypes().contains(DECT400_THING_TYPE)); + assertTrue(discovery.getSupportedThingTypes().contains(DECT440_THING_TYPE)); assertTrue(discovery.getSupportedThingTypes().contains(PL546E_THING_TYPE)); assertTrue(discovery.getSupportedThingTypes().contains(COMETDECT_THING_TYPE)); assertTrue(discovery.getSupportedThingTypes().contains(HAN_FUN_CONTACT_THING_TYPE));