added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.avmfritz</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@@ -0,0 +1,310 @@
# 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.
## Supported Things
### FRITZ!Box
FRITZ!Boxes (thing type `fritzbox`) are supported as bridges and they offer channels for call monitoring.
To activate the call monitor interface on a FRITZ!Box, you need to dial once `#96*5*` on a connected telephone.
(It can be deactivated again by dialing `#96*4*`.)
Additionally, they serve as a bridge for accessing other AHA devices.
For AHA functionality, the router has to run at least on firmware FRITZ!OS 6.00 and it has to support the "Smart Home" service.
### FRITZ!DECT 200 / FRITZ!DECT 210
This switchable outlets [FRITZ!DECT 210](https://avm.de/produkte/fritzdect/fritzdect-210/) and [FRITZ!DECT 200](https://avm.de/produkte/fritzdect/fritzdect-200/) have to be connected to a FRITZ!Box by DECT protocol.
They support switching the outlet and reading the current power, current voltage, accumulated energy consumption and temperature.
**NOTE:** The `voltage` channel will be added to the thing during runtime - if the interface supports it (FRITZ!OS 7 or higher).
### FRITZ!DECT Repeater 100
This [DECT repeater](https://avm.de/produkte/fritzdect/fritzdect-repeater-100/) has to be connected to a FRITZ!Box by DECT protocol.
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.
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.
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
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).
### DECT-ULE / HAN-FUN Devices
The following sensors have been successfully tested using FRITZ!OS 7 for FRITZ!Box 7490 / 7590:
- [SmartHome Tür-/Fensterkontakt (optisch)](https://www.smarthome.de/geraete/eurotronic-smarthome-tuer-fensterkontakt-optisch) - an optical door/window contact
- [SmartHome Tür-/Fensterkontakt (magnetisch)](https://www.smarthome.de/geraete/smarthome-tuer-fensterkontakt-magnetisch-weiss) - a magnetic door/window contact
- [SmartHome Bewegungsmelder](https://www.smarthome.de/geraete/telekom-smarthome-bewegungsmelder-innen) - a motion sensor
- [SmartHome Rauchmelder](https://www.smarthome.de/geraete/smarthome-rauchmelder-weiss) - a smoke detector
- [SmartHome Wandtaster](https://www.smarthome.de/geraete/telekom-smarthome-wandtaster) - a switch with two buttons
The use of other Sensors should be possible, if these are compatible with DECT-ULE / HAN-FUN standards.
The FRITZ!Box has to run at least on firmware FRITZ!OS 7.
### FRITZ! Groups
The FRITZ!OS supports two different types of groups.
On the one hand there are groups for heating thermostats on the other hand there are groups for switchable outlets and power meters.
The first one provides the same channels and actions like the [FRITZ!DECT 301 / FRITZ!DECT 300 / Comet DECT](https://www.openhab.org/addons/bindings/avmfritz/#fritz-dect-301-fritz-dect-300-comet-dect) devices.
The latter provides the same channels like the [FRITZ!DECT 200 / FRITZ!DECT 210](https://www.openhab.org/addons/bindings/avmfritz/#fritz-dect-200-fritz-dect-210) / [FRITZ!Powerline 546E](https://www.openhab.org/addons/bindings/avmfritz/#fritz-powerline-546e) devices.
The FRITZ!Box has to run at least on firmware FRITZ!OS 6.69.
## Discovery
The FRITZ!Box and the powerline adapter are discovered through UPnP in the local network.
When added as things, a username/password has eventually to be set depending on your Box/Powerline security configuration.
The credentials given in the settings must have HomeAuto permissions.
This implies to enable "login to the home network with user name and password" setting in the FRITZ!Box.
To do so
- Click "System" in the FRITZ!Box user interface.
- Click "FRITZ!Box Users" in the "System" menu.
- Click on the "Login to the Home Network" tab.
- 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.
Auto-discovery is enabled by default.
To disable it, you can add the following line to `<openHAB-conf>/services/runtime.cfg`:
```
discovery.avmfritz:background=false
```
If correct credentials are set in the bridge configuration, connected AHA devices are discovered automatically (may last up to 3 minutes).
## Thing Configuration
### FRITZ!Box
- `ipAddress` (mandatory), default "fritz.box"
- `protocol` (optional, "http" or "https"), default "http"
- `port` (optional, 1 to 65535), no default (derived from protocol: 80 or 443)
- `password` (optional for call monitoring, but mandatory for AHA features), no default (depends on FRITZ!Box security configuration)
- `user` (optional), no default (depends on FRITZ!Box security configuration)
- `pollingInterval` (optional, 5 to 60), default 15 (in seconds)
- `asyncTimeout` (optional, 1000 to 60000), default 10000 (in milliseconds)
- `syncTimeout` (optional, 500 to 15000), default 2000 (in milliseconds)
### FRITZ!Powerline 546E
- `ain` (optional, advanced), no default (AIN number of the device)
- `ipAddress` (mandatory), default "fritz.powerline"
- `protocol` (optional, "http" or "https"), default "http"
- `port` (optional, 1 to 65535), no default (derived from protocol: 80 or 443)
- `password` (optional), no default (depends on FRITZ!Powerline security configuration)
- `pollingInterval` (optional, 5 to 60), default 15 (in seconds)
- `asyncTimeout` (optional, 1000 to 60000), default 10000 (in milliseconds)
- `syncTimeout` (optional, 500 to 15000), default 2000 (in milliseconds)
If the FRITZ!Powerline 546E is added via auto-discovery it determines its own `ain`, otherwise you have to configure it manually.
### Things Connected To FRITZ!Box Or FRITZ!Powerline 546E
- `ain` (mandatory), no default (AIN number of the device)
### 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 |
### 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 |
The trigger channel `press` is of type `system.rawbutton` to allow the usage of the `rawbutton-toggle-switch` profile.
### FRITZ! Smart Home Templates
With the new [templates feature](https://en.avm.de/guide/smart-home/meet-the-smart-home-templates-from-fritz/) in FRITZ!OS 7, you can now save the settings of your Smart Home devices and groups as a template for certain occasions e.g. holidays or vacation.
Unfortunately it is not that simple to find out the unique identifier (AIN) for a template needed for sending it as command to the `apply_template` channel.
Here is a work-around:
To retrieve the list of AINs assigned by FRITZ! for your templates, go to the FRITZ!Box' Support page at http://fritz.box/html/support.html within your local network and login.
Then in the section "Support Data" ("Support-Daten") press the button "Create Support Data" ("Support-Daten erstellen") and save the generated text file.
Open the file in a text editor and search for the term "avm_home_device_type_template".
You will find entries like the attached one.
The `identifyer 'tmpFC0F2C-3960B7EE6'` contains the templates AINs you need for using them in rules.
```
Name 'Demo Template', identifyer 'tmpFC0F2C-3960B7EE6', firmware version '0.1'
[aktive] ID 60013, emc 0x0, model 0x0, grouphash=0x0, devicetype 'avm_home_device_type_template', functionbitmask 0x4000, sortid 0, batt perc 255 low 255, pollinterval 0, polltimeout 0, validchangetime: 0
--------------------
```
templates.rules
```java
rule "Apply template"
when
...
then
ApplyTemplate.sendCommand("tmpFC0F2C-3960B7EE6")
end
```
### Actions
For heating devices and heating groups there are two actions available to set Boost or Window Open mode for a given duration: `setBoostMode(long)` and `setWindowOpenMode(long)`.
The duration has to be given in seconds, min. 1, max. 86400, 0 for deactivation.
```
val actions = getActions("avmfritz","avmfritz:Comet_DECT:1:aaaaaabbbbbb")
// set Boost mode for 5 min
actions.setBoostMode(300)
```
## Full Example
demo.things:
```java
Bridge avmfritz:fritzbox:1 "FRITZ!Box" [ ipAddress="192.168.x.x", password="xxx", user="xxx" ] {
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" ]
Thing HAN_FUN_CONTACT zzzzzzzzzzzz_1 "HAN-FUN Contact #4" [ ain="zzzzzzzzzzzz-1" ]
Thing HAN_FUN_SWITCH zzzzzzzzzzzz_2 "HAN-FUN Switch #5" [ ain=zzzzzzzzzzzz-2" ]
Thing FRITZ_DECT_Repeater_100 rrrrrrrrrrrr "DECT Repeater 100 #6" [ ain="rrrrrrrrrrrr" ]
Thing FRITZ_GROUP_HEATING AA_AA_AA_900 "Heating group" [ ain="AA:AA:AA-900" ]
Thing FRITZ_GROUP_SWITCH BB_BB_BB_900 "Switch group" [ ain="BB:BB:BB-900" ]
}
```
demo.items:
```java
String CallState "Call State [%s]" { channel="avmfritz:fritzbox:1:call_state" }
Call IncomingCall "Incoming call: [%1$s to %2$s]" { channel="avmfritz:fritzbox:1:incoming_call" }
Call OutgoingCall "Outgoing call: [%1$s to %2$s]" { channel="avmfritz:fritzbox:1:outgoing_call" }
Call ActiveCall "Call established [%1$s]" { channel="avmfritz:fritzbox:1:active_call" }
String ApplyTemplate "Apply template" { channel="avmfritz:fritzbox:1:apply_template" }
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" }
Number:Power Power1 "Current power consumption [%.2f %unit%]" { channel="avmfritz:FRITZ_DECT_200:1:xxxxxxxxxxxx:power" }
Number:ElectricPotential Voltage1 "Current voltage [%.1f %unit%]" { channel="avmfritz:FRITZ_DECT_200:1:xxxxxxxxxxxx:voltage" }
Switch Outlet2 "Switchable outlet" { channel="avmfritz:FRITZ_Powerline_546E:1:yy_yy_yy_yy_yy_yy:outlet" }
Number:Temperature COMETDECTTemperature "Current measured temperature [%.1f %unit%]" { channel="avmfritz:Comet_DECT:1:aaaaaabbbbbb:actual_temp" }
Number:Temperature COMETDECTSetTemperature "Thermostat temperature set point [%.1f %unit%]" { channel="avmfritz:Comet_DECT:1:aaaaaabbbbbb:set_temp" }
String COMETDECTRadiatorMode "Radiator mode [%s]" { channel="avmfritz:Comet_DECT:1:aaaaaabbbbbb:radiator_mode" }
Number COMETDECTBattery "Battery level" { channel="avmfritz:Comet_DECT:1:aaaaaabbbbbb:battery_level" }
Switch COMETDECTBatteryLow "Battery low" { channel="avmfritz:Comet_DECT:1:aaaaaabbbbbb:battery_low" }
Contact HANFUNContactState "Status [%s]" { channel="avmfritz:HAN_FUN_CONTACT:1:zzzzzzzzzzzz_1:contact_state" }
DateTime HANFUNSwitchLastChanged "Last change" { channel="avmfritz:HAN_FUN_SWITCH:1:zzzzzzzzzzzz_2:last_change" }
Number:Temperature Temperature1 "Current measured temperature [%.1f %unit%]" { channel="avmfritz:FRITZ_DECT_Repeater_100:1:rrrrrrrrrrrr:temperature" }
Number:Temperature FRITZ_GROUP_HEATINGSetTemperature "Group temperature set point [%.1f %unit%]" { channel="avmfritz:FRITZ_GROUP_HEATING:1:AA_AA_AA_900:set_temp" }
Switch Outlet3 "Group switch" { channel="avmfritz:FRITZ_GROUP_SWITCH:1:BB_BB_BB_900:outlet" }
```
demo.sitemap:
```java
sitemap demo label="Main Menu" {
Frame label="FRITZ!Box" {
Text item=CallState
Text item=IncomingCall
Text item=OutgoingCall
Text item=ActiveCall
Selection item=ApplyTemplate
}
Frame label="FRITZ!DECT 200 switchable outlet" {
Switch item=Outlet1 icon="poweroutlet"
Text item=Temperature1 icon="temperature"
Text item=Energy1 icon="energy"
Text item=Power1 icon="energy"
Text item=Voltage1 icon="energy"
}
Frame label="FRITZ!Powerline 546E switchable outlet" {
Switch item=Outlet2 icon="poweroutlet"
}
Frame label="Comet DECT heating thermostat" {
Text item=COMETDECTTemperature icon="temperature"
Setpoint item=COMETDECTSetTemperature minValue=8.0 maxValue=28.0 step=0.5 icon="temperature"
Selection item=COMETDECTRadiatorMode mappings=["ON"="ON", "OFF"="OFF", "COMFORT"="COMFORT", "ECO"="ECO", "BOOST"="BOOST"] icon="heating"
Text item=COMETDECTBattery icon="battery"
Switch item=COMETDECTBatteryLow icon="lowbattery"
}
Frame label="HAN-FUN Contact" {
Text item=HANFUNContactState
}
Frame label="HAN-FUN Switch" {
Text item=HANFUNSwitchLastChanged
}
}
```
demo.rules:
```java
rule "HAN-FUN Button pressed"
when
Channel "avmfritz:HAN_FUN_SWITCH:1:zzzzzzzzzzzz_2:press" triggered
then
logInfo("demo", "Button pressed")
end
```

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.avmfritz</artifactId>
<name>openHAB Add-ons :: Bundles :: AVM FRITZ! Binding</name>
</project>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.avmfritz-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-avmfritz" description="AVM FRITZ!Box Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-upnp</feature>
<feature dependency="true">openhab.tp-jaxb</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.avmfritz/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,107 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.actions;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.avmfritz.internal.actions.IAVMFritzHeatingActions;
import org.openhab.binding.avmfritz.internal.handler.AVMFritzHeatingActionsHandler;
import org.openhab.core.automation.annotation.ActionInput;
import org.openhab.core.automation.annotation.RuleAction;
import org.openhab.core.thing.binding.ThingActions;
import org.openhab.core.thing.binding.ThingActionsScope;
import org.openhab.core.thing.binding.ThingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link AVMFritzHeatingActions} defines thing actions for heating devices / groups of the avmfritz binding.
*
* @author Christoph Weitkamp - Initial contribution
*/
@ThingActionsScope(name = "avmfritz")
@NonNullByDefault
public class AVMFritzHeatingActions implements ThingActions, IAVMFritzHeatingActions {
private final Logger logger = LoggerFactory.getLogger(AVMFritzHeatingActions.class);
private @Nullable AVMFritzHeatingActionsHandler handler;
@Override
public void setThingHandler(@Nullable ThingHandler handler) {
this.handler = (AVMFritzHeatingActionsHandler) handler;
}
@Override
public @Nullable ThingHandler getThingHandler() {
return handler;
}
@Override
@RuleAction(label = "@text/setBoostModeModeActionLabel", description = "@text/setBoostModeActionDescription")
public void setBoostMode(
@ActionInput(name = "Duration", label = "@text/setBoostModeDurationInputLabel", description = "@text/setBoostModeDurationInputDescription", type = "java.lang.Long", required = true) @Nullable Long duration) {
AVMFritzHeatingActionsHandler actionsHandler = handler;
if (actionsHandler == null) {
throw new IllegalArgumentException("AVMFritzHeatingActions ThingHandler is null!");
}
if (duration == null) {
throw new IllegalArgumentException("Cannot set Boost mode as 'duration' is null!");
}
actionsHandler.setBoostMode(duration.longValue());
}
public static void setBoostMode(@Nullable ThingActions actions, @Nullable Long duration) {
invokeMethodOf(actions).setBoostMode(duration);
}
@Override
@RuleAction(label = "@text/setWindowOpenModeActionLabel", description = "@text/setWindowOpenModeActionDescription")
public void setWindowOpenMode(
@ActionInput(name = "Duration", label = "@text/setWindowOpenModeDurationInputLabel", description = "@text/setWindowOpenModeDurationInputDescription", type = "java.lang.Long", required = true) @Nullable Long duration) {
AVMFritzHeatingActionsHandler actionsHandler = handler;
if (actionsHandler == null) {
throw new IllegalArgumentException("AVMFritzHeatingActions ThingHandler is null!");
}
if (duration == null) {
throw new IllegalArgumentException("Cannot set Window Open mode as 'duration' is null!");
}
actionsHandler.setWindowOpenMode(duration.longValue());
}
public static void setWindowOpenMode(@Nullable ThingActions actions, @Nullable Long duration) {
invokeMethodOf(actions).setWindowOpenMode(duration);
}
private static IAVMFritzHeatingActions invokeMethodOf(@Nullable ThingActions actions) {
if (actions == null) {
throw new IllegalArgumentException("actions cannot be null");
}
if (actions.getClass().getName().equals(AVMFritzHeatingActions.class.getName())) {
if (actions instanceof IAVMFritzHeatingActions) {
return (IAVMFritzHeatingActions) actions;
} else {
return (IAVMFritzHeatingActions) Proxy.newProxyInstance(IAVMFritzHeatingActions.class.getClassLoader(),
new Class[] { IAVMFritzHeatingActions.class }, (Object proxy, Method method, Object[] args) -> {
Method m = actions.getClass().getDeclaredMethod(method.getName(),
method.getParameterTypes());
return m.invoke(actions, args);
});
}
}
throw new IllegalArgumentException("Actions is not an instance of AVMFritzHeatingActions");
}
}

View File

@@ -0,0 +1,168 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ThingTypeUID;
/**
* This class defines common constants, which are used across the whole binding.
*
* @author Robert Bausdorf - Initial contribution
* @author Christoph Weitkamp - Added support for AVM FRITZ!DECT 300 and Comet DECT
* @author Christoph Weitkamp - Added support for groups
* @author Christoph Weitkamp - Added channels 'voltage' and 'battery_level'
*/
@NonNullByDefault
public class AVMFritzBindingConstants {
public static final String INVALID_PATTERN = "[^a-zA-Z0-9_]";
public static final String BINDING_ID = "avmfritz";
public static final String BRIDGE_FRITZBOX = "fritzbox";
public static final String BOX_MODEL_NAME = "FRITZ!Box";
public static final String POWERLINE_MODEL_NAME = "FRITZ!Powerline";
// List of main device types
public static final String DEVICE_DECT400 = "FRITZ_DECT_400";
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";
public static final String DEVICE_DECT200 = "FRITZ_DECT_200";
public static final String DEVICE_DECT100 = "FRITZ_DECT_Repeater_100";
public static final String DEVICE_PL546E = "FRITZ_Powerline_546E";
public static final String DEVICE_PL546E_STANDALONE = "FRITZ_Powerline_546E_Solo";
public static final String DEVICE_COMETDECT = "Comet_DECT";
public static final String DEVICE_HAN_FUN_CONTACT = "HAN_FUN_CONTACT";
public static final String DEVICE_HAN_FUN_SWITCH = "HAN_FUN_SWITCH";
// List of main group types
public static final String GROUP_HEATING = "FRITZ_GROUP_HEATING";
public static final String GROUP_SWITCH = "FRITZ_GROUP_SWITCH";
// 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 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);
public static final ThingTypeUID DECT200_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_DECT200);
public static final ThingTypeUID DECT100_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_DECT100);
public static final ThingTypeUID PL546E_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_PL546E);
public static final ThingTypeUID PL546E_STANDALONE_THING_TYPE = new ThingTypeUID(BINDING_ID,
DEVICE_PL546E_STANDALONE);
public static final ThingTypeUID COMETDECT_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_COMETDECT);
public static final ThingTypeUID HAN_FUN_CONTACT_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_HAN_FUN_CONTACT);
public static final ThingTypeUID HAN_FUN_SWITCH_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_HAN_FUN_SWITCH);
public static final ThingTypeUID GROUP_HEATING_THING_TYPE = new ThingTypeUID(BINDING_ID, GROUP_HEATING);
public static final ThingTypeUID GROUP_SWITCH_THING_TYPE = new ThingTypeUID(BINDING_ID, GROUP_SWITCH);
// List of all Thing config ids
public static final String CONFIG_IP_ADDRESS = "ipAddress";
public static final String CONFIG_PROTOCOL = "protocol";
public static final String CONFIG_USER = "user";
public static final String CONFIG_PASSWORD = "password";
public static final String CONFIG_POLLING_INTERVAL = "pollingInterval";
public static final String CONFIG_SYNC_TIMEOUT = "syncTimeout";
public static final String CONFIG_AIN = "ain";
// List of all Properties
public static final String PROPERTY_MASTER = "master";
public static final String PROPERTY_MEMBERS = "members";
// List of all Channel ids
public static final String CHANNEL_CALL_INCOMING = "incoming_call";
public static final String CHANNEL_CALL_OUTGOING = "outgoing_call";
public static final String CHANNEL_CALL_ACTIVE = "active_call";
public static final String CHANNEL_CALL_STATE = "call_state";
public static final String CHANNEL_MODE = "mode";
public static final String CHANNEL_LOCKED = "locked";
public static final String CHANNEL_DEVICE_LOCKED = "device_locked";
public static final String CHANNEL_APPLY_TEMPLATE = "apply_template";
public static final String CHANNEL_TEMPERATURE = "temperature";
public static final String CHANNEL_ENERGY = "energy";
public static final String CHANNEL_POWER = "power";
public static final String CHANNEL_VOLTAGE = "voltage";
public static final String CHANNEL_OUTLET = "outlet";
public static final String CHANNEL_ACTUALTEMP = "actual_temp";
public static final String CHANNEL_SETTEMP = "set_temp";
public static final String CHANNEL_ECOTEMP = "eco_temp";
public static final String CHANNEL_COMFORTTEMP = "comfort_temp";
public static final String CHANNEL_RADIATOR_MODE = "radiator_mode";
public static final String CHANNEL_NEXT_CHANGE = "next_change";
public static final String CHANNEL_NEXTTEMP = "next_temp";
public static final String CHANNEL_BATTERY_LOW = "battery_low";
public static final String CHANNEL_BATTERY = "battery_level";
public static final String CHANNEL_CONTACT_STATE = "contact_state";
public static final String CHANNEL_PRESS = "press";
public static final String CHANNEL_LAST_CHANGE = "last_change";
// List of all Channel config ids
public static final String CONFIG_CHANNEL_TEMP_OFFSET = "offset";
// List of all Input tags
public static final String INPUT_PRESENT = "present";
public static final String INPUT_ACTUALTEMP = "tist";
public static final String INPUT_SETTEMP = "tsoll";
public static final String INPUT_ECOTEMP = "absenk";
public static final String INPUT_COMFORTTEMP = "komfort";
public static final String INPUT_NEXTCHANGE = "endperiod";
public static final String INPUT_NEXTTEMP = "tchange";
public static final String INPUT_BATTERY = "batterylow";
// List of all call states
public static final StringType CALL_STATE_IDLE = new StringType("IDLE");
public static final StringType CALL_STATE_RINGING = new StringType("RINGING");
public static final StringType CALL_STATE_DIALING = new StringType("DIALING");
public static final StringType CALL_STATE_ACTIVE = new StringType("ACTIVE");
// List of all Mode types
public static final String MODE_AUTO = "AUTOMATIC";
public static final String MODE_MANUAL = "MANUAL";
public static final String MODE_VACATION = "VACATION";
public static final String MODE_ON = "ON";
public static final String MODE_OFF = "OFF";
public static final String MODE_COMFORT = "COMFORT";
public static final String MODE_ECO = "ECO";
public static final String MODE_BOOST = "BOOST";
public static final String MODE_WINDOW_OPEN = "WINDOW_OPEN";
public static final String MODE_UNKNOWN = "UNKNOWN";
public static final Set<ThingTypeUID> SUPPORTED_BUTTON_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(DECT400_THING_TYPE, HAN_FUN_SWITCH_THING_TYPE).collect(Collectors.toSet()));
public static final Set<ThingTypeUID> SUPPORTED_HEATING_THING_TYPES = Collections.unmodifiableSet(
Stream.of(DECT300_THING_TYPE, DECT301_THING_TYPE, COMETDECT_THING_TYPE).collect(Collectors.toSet()));
public static final Set<ThingTypeUID> 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<ThingTypeUID> SUPPORTED_GROUP_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(GROUP_HEATING_THING_TYPE, GROUP_SWITCH_THING_TYPE).collect(Collectors.toSet()));
public static final Set<ThingTypeUID> SUPPORTED_BRIDGE_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(BRIDGE_THING_TYPE, PL546E_STANDALONE_THING_TYPE).collect(Collectors.toSet()));
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(Stream
.of(SUPPORTED_BUTTON_THING_TYPES_UIDS, SUPPORTED_HEATING_THING_TYPES, SUPPORTED_DEVICE_THING_TYPES_UIDS,
SUPPORTED_GROUP_THING_TYPES_UIDS, SUPPORTED_BRIDGE_THING_TYPES_UIDS)
.flatMap(Set::stream).collect(Collectors.toSet()));
}

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.binding.BaseDynamicCommandDescriptionProvider;
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
import org.openhab.core.thing.type.DynamicCommandDescriptionProvider;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* Dynamic provider of command options.
*
* @author Christoph Weitkamp - Initial contribution
*/
@Component(service = { DynamicCommandDescriptionProvider.class, AVMFritzDynamicCommandDescriptionProvider.class })
@NonNullByDefault
public class AVMFritzDynamicCommandDescriptionProvider extends BaseDynamicCommandDescriptionProvider {
@Activate
public AVMFritzDynamicCommandDescriptionProvider(
final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
}
}

View File

@@ -0,0 +1,94 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal;
import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.avmfritz.internal.handler.AVMFritzButtonHandler;
import org.openhab.binding.avmfritz.internal.handler.AVMFritzHeatingDeviceHandler;
import org.openhab.binding.avmfritz.internal.handler.AVMFritzHeatingGroupHandler;
import org.openhab.binding.avmfritz.internal.handler.BoxHandler;
import org.openhab.binding.avmfritz.internal.handler.DeviceHandler;
import org.openhab.binding.avmfritz.internal.handler.GroupHandler;
import org.openhab.binding.avmfritz.internal.handler.Powerline546EHandler;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link AVMFritzHandlerFactory} is responsible for creating things and thing handlers.
*
* @author Robert Bausdorf - Initial contribution
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.avmfritz")
@NonNullByDefault
public class AVMFritzHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(AVMFritzHandlerFactory.class);
private final HttpClient httpClient;
private final AVMFritzDynamicCommandDescriptionProvider commandDescriptionProvider;
@Activate
public AVMFritzHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
final @Reference AVMFritzDynamicCommandDescriptionProvider stateDescriptionProvider) {
this.httpClient = httpClientFactory.getCommonHttpClient();
this.commandDescriptionProvider = stateDescriptionProvider;
}
/**
* Provides the supported thing types
*/
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
/**
* Create handler of things.
*/
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (BRIDGE_THING_TYPE.equals(thingTypeUID)) {
return new BoxHandler((Bridge) thing, httpClient, commandDescriptionProvider);
} else if (PL546E_STANDALONE_THING_TYPE.equals(thingTypeUID)) {
return new Powerline546EHandler((Bridge) thing, httpClient, commandDescriptionProvider);
} else if (SUPPORTED_BUTTON_THING_TYPES_UIDS.contains(thingTypeUID)) {
return new AVMFritzButtonHandler(thing);
} else if (SUPPORTED_HEATING_THING_TYPES.contains(thingTypeUID)) {
return new AVMFritzHeatingDeviceHandler(thing);
} else if (SUPPORTED_DEVICE_THING_TYPES_UIDS.contains(thingTypeUID)) {
return new DeviceHandler(thing);
} else if (GROUP_HEATING_THING_TYPE.equals(thingTypeUID)) {
return new AVMFritzHeatingGroupHandler(thing);
} else if (SUPPORTED_GROUP_THING_TYPES_UIDS.contains(thingTypeUID)) {
return new GroupHandler(thing);
} else {
logger.error("ThingHandler not found for {}", thingTypeUID);
}
return null;
}
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal;
import javax.net.ssl.X509ExtendedTrustManager;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.io.net.http.TlsTrustManagerProvider;
import org.openhab.core.io.net.http.TrustAllTrustMananger;
import org.osgi.service.component.annotations.Component;
/**
* Provides a TrustManager to allow secure connections to any FRITZ!Box
*
* @author Chritoph Weitkamp - Initial Contribution
*/
@Component
@NonNullByDefault
public class AVMFritzTlsTrustManagerProvider implements TlsTrustManagerProvider {
@Override
public String getHostName() {
return "fritz.box";
}
@Override
public X509ExtendedTrustManager getTrustManager() {
return TrustAllTrustMananger.getInstance();
}
}

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.actions;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.avmfritz.actions.AVMFritzHeatingActions;
/**
* The {@link IAVMFritzHeatingActions} defines the interface for all thing actions supported by the binding.
* These methods, parameters, and return types are explained in {@link AVMFritzHeatingActions}.
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public interface IAVMFritzHeatingActions {
void setBoostMode(@Nullable Long duration);
void setWindowOpenMode(@Nullable Long duration);
}

View File

@@ -0,0 +1,111 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.callmonitor;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Call Events received from a fritzbox.
*
* 12.07.20 09:11:30;RING;0;0171123456;888888;SIP2;
* 12.07.20 09:13:40;DISCONNECT;0;0;
*
* @author Kai Kreuzer - Initial contribution
*/
@NonNullByDefault
public class CallEvent {
private final String rawEvent;
private final String timestamp;
private final String callType;
private final String id;
private @Nullable String externalNo;
private @Nullable String internalNo;
private @Nullable String connectionType;
private @Nullable String line;
public CallEvent(String rawEvent) {
this.rawEvent = rawEvent;
String[] fields = rawEvent.split(";");
if (fields.length < 4) {
throw new IllegalArgumentException("Cannot parse call event: " + rawEvent);
}
timestamp = fields[0];
callType = fields[1];
id = fields[2];
if (callType.equals("RING")) {
externalNo = fields[3];
internalNo = fields[4];
connectionType = fields[5];
} else if (callType.equals("CONNECT")) {
line = fields[3];
if (fields.length > 4) {
externalNo = fields[4];
} else {
externalNo = "Unknown";
}
} else if (callType.equals("CALL")) {
line = fields[3];
internalNo = fields[4];
externalNo = fields[5];
connectionType = fields[6];
} else if (callType.equals("DISCONNECT")) {
// no fields to set
} else {
throw new IllegalArgumentException("Invalid call type: " + callType);
}
}
public @Nullable String getLine() {
return line;
}
public String getTimestamp() {
return timestamp;
}
public String getCallType() {
return callType;
}
public String getId() {
return id;
}
public @Nullable String getExternalNo() {
return externalNo;
}
public @Nullable String getInternalNo() {
return internalNo;
}
public @Nullable String getConnectionType() {
return connectionType;
}
public String getRaw() {
return rawEvent;
}
@Override
public String toString() {
return "CallEvent [timestamp=" + timestamp + ", callType=" + callType + ", id=" + id + ", externalNo="
+ externalNo + ", internalNo=" + internalNo + ", connectionType=" + connectionType + ", line=" + line
+ "]";
}
}

View File

@@ -0,0 +1,222 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.callmonitor;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants;
import org.openhab.binding.avmfritz.internal.handler.BoxHandler;
import org.openhab.core.library.types.StringListType;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class handles all communication with the call monitor port of the fritzbox.
*
* @author Kai Kreuzer - Initial contribution
*/
@NonNullByDefault
public class CallMonitor {
protected final Logger logger = LoggerFactory.getLogger(CallMonitor.class);
// port number to connect to fritzbox
private final int MONITOR_PORT = 1012;
private @Nullable CallMonitorThread monitorThread;
private ScheduledFuture<?> reconnectJob;
private String ip;
private BoxHandler handler;
public CallMonitor(String ip, BoxHandler handler, ScheduledExecutorService scheduler) {
this.ip = ip;
this.handler = handler;
reconnectJob = scheduler.scheduleWithFixedDelay(() -> {
stopThread();
// Wait before reconnect
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
}
// create a new thread for listening to the FritzBox
CallMonitorThread thread = new CallMonitorThread();
thread.setName("OH-binding-" + handler.getThing().getUID().getAsString());
thread.start();
this.monitorThread = thread;
}, 0, 2, TimeUnit.HOURS);
}
/**
* Cancel the reconnect job.
*/
public void dispose() {
reconnectJob.cancel(true);
}
public class CallMonitorThread extends Thread {
// Socket to connect
private @Nullable Socket socket;
// Thread control flag
private boolean interrupted = false;
// time to wait before reconnecting
private long reconnectTime = 60000L;
public CallMonitorThread() {
}
@Override
public void run() {
while (!interrupted) {
BufferedReader reader = null;
try {
logger.debug("Callmonitor thread [{}] attempting connection to FritzBox on {}:{}.",
Thread.currentThread().getId(), ip, MONITOR_PORT);
socket = new Socket(ip, MONITOR_PORT);
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// reset the retry interval
reconnectTime = 60000L;
} catch (Exception e) {
handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Cannot connect to Fritz!Box call monitor - make sure to enable it by dialing '#96*5'!");
logger.debug("Error attempting to connect to FritzBox. Retrying in {} seconds",
reconnectTime / 1000L, e);
try {
Thread.sleep(reconnectTime);
} catch (InterruptedException ex) {
interrupted = true;
}
// wait another more minute the next time
reconnectTime += 60000L;
}
if (reader != null) {
logger.debug("Connected to FritzBox call monitor at {}:{}.", ip, MONITOR_PORT);
handler.setStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
while (!interrupted) {
try {
String line = reader.readLine();
if (line != null) {
logger.debug("Received raw call string from fbox: {}", line);
CallEvent ce = new CallEvent(line);
handleCallEvent(ce);
}
} catch (IOException e) {
if (interrupted) {
logger.debug("Lost connection to Fritzbox because of an interrupt.");
} else {
handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Lost connection to Fritz!Box: " + e.getMessage());
}
break;
} finally {
try {
sleep(1000L);
} catch (InterruptedException e) {
}
}
}
}
}
}
/**
* Close socket and stop running thread.
*/
@Override
public void interrupt() {
interrupted = true;
if (socket != null) {
try {
socket.close();
logger.debug("Socket to FritzBox closed.");
} catch (IOException e) {
logger.warn("Failed to close connection to FritzBox.", e);
}
} else {
logger.debug("Socket to FritzBox not open, therefore not closing it.");
}
}
/**
* Handle call event and update item as required.
*
* @param ce call event to process
*/
private void handleCallEvent(CallEvent ce) {
if (ce.getCallType().equals("DISCONNECT")) {
// reset states of call monitor channels
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_INCOMING, UnDefType.UNDEF);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_OUTGOING, UnDefType.UNDEF);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_ACTIVE, UnDefType.UNDEF);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_STATE,
AVMFritzBindingConstants.CALL_STATE_IDLE);
} else if (ce.getCallType().equals("RING")) { // first event when call is incoming
StringListType state = new StringListType(ce.getInternalNo(), ce.getExternalNo());
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_INCOMING, state);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_OUTGOING, UnDefType.UNDEF);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_ACTIVE, UnDefType.UNDEF);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_STATE,
AVMFritzBindingConstants.CALL_STATE_RINGING);
} else if (ce.getCallType().equals("CONNECT")) { // when call is answered/running
StringListType state = new StringListType(ce.getExternalNo(), "");
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_ACTIVE, state);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_INCOMING, UnDefType.UNDEF);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_OUTGOING, UnDefType.UNDEF);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_STATE,
AVMFritzBindingConstants.CALL_STATE_ACTIVE);
} else if (ce.getCallType().equals("CALL")) { // outgoing call
StringListType state = new StringListType(ce.getExternalNo(), ce.getInternalNo());
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_INCOMING, UnDefType.UNDEF);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_OUTGOING, state);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_ACTIVE, UnDefType.UNDEF);
handler.updateState(AVMFritzBindingConstants.CHANNEL_CALL_STATE,
AVMFritzBindingConstants.CALL_STATE_DIALING);
}
}
}
public void stopThread() {
logger.debug("Stopping call monitor thread...");
if (monitorThread != null) {
monitorThread.interrupt();
monitorThread = null;
}
}
public void startThread() {
logger.debug("Starting call monitor thread...");
if (monitorThread != null) {
monitorThread.interrupt();
monitorThread = null;
}
// create a new thread for listening to the FritzBox
monitorThread = new CallMonitorThread();
monitorThread.start();
}
}

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Bean holding configuration data for FRITZ! Box.
*
* @author Robert Bausdorf - Initial contribution
*/
@NonNullByDefault
public class AVMFritzBoxConfiguration {
public @NonNullByDefault({}) String ipAddress;
public @Nullable Integer port;
public String protocol = "http";
public @Nullable String user;
public @NonNullByDefault({}) String password;
public long pollingInterval = 15;
public long asyncTimeout = 10000;
public long syncTimeout = 2000;
@Override
public String toString() {
return new StringBuilder().append("[IP=").append(ipAddress).append(",port=").append(port).append(",protocol=")
.append(protocol).append(",user=").append(user).append(",pollingInterval=").append(pollingInterval)
.append(",asyncTimeout=").append(asyncTimeout).append(",syncTimeout=").append(syncTimeout).append("]")
.toString();
}
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Bean holding configuration data for FRITZ! devices.
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class AVMFritzDeviceConfiguration {
public @Nullable String ain;
@Override
public String toString() {
return new StringBuilder().append("[identifier=").append(ain).append("]").toString();
}
}

View File

@@ -0,0 +1,147 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.discovery;
import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.*;
import static org.openhab.core.thing.Thing.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
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.GroupModel;
import org.openhab.binding.avmfritz.internal.handler.AVMFritzBaseBridgeHandler;
import org.openhab.binding.avmfritz.internal.hardware.FritzAhaStatusListener;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Discover all AHA (AVM Home Automation) devices connected to a FRITZ!Box device.
*
* @author Robert Bausdorf - Initial contribution
* @author Christoph Weitkamp - Added support for groups
*/
@NonNullByDefault
public class AVMFritzDiscoveryService extends AbstractDiscoveryService
implements FritzAhaStatusListener, DiscoveryService, ThingHandlerService {
private final Logger logger = LoggerFactory.getLogger(AVMFritzDiscoveryService.class);
/**
* Handler of the bridge of which devices have to be discovered.
*/
private @NonNullByDefault({}) AVMFritzBaseBridgeHandler bridgeHandler;
public AVMFritzDiscoveryService() {
super(Collections
.unmodifiableSet(Stream
.of(SUPPORTED_BUTTON_THING_TYPES_UIDS, SUPPORTED_HEATING_THING_TYPES,
SUPPORTED_DEVICE_THING_TYPES_UIDS, SUPPORTED_GROUP_THING_TYPES_UIDS)
.flatMap(Set::stream).collect(Collectors.toSet())),
30);
}
@Override
public void activate() {
super.activate(null);
bridgeHandler.registerStatusListener(this);
}
@Override
public void deactivate() {
bridgeHandler.unregisterStatusListener(this);
super.deactivate();
}
@Override
public void startScan() {
logger.debug("Start manual scan on bridge {}", bridgeHandler.getThing().getUID());
bridgeHandler.handleRefreshCommand();
}
@Override
protected synchronized void stopScan() {
logger.debug("Stop manual scan on bridge {}", bridgeHandler.getThing().getUID());
super.stopScan();
}
@Override
public void setThingHandler(@NonNullByDefault({}) ThingHandler handler) {
if (handler instanceof AVMFritzBaseBridgeHandler) {
bridgeHandler = (AVMFritzBaseBridgeHandler) handler;
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return bridgeHandler;
}
@Override
public void onDeviceAdded(AVMFritzBaseModel device) {
ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, bridgeHandler.getThingTypeId(device));
if (getSupportedThingTypes().contains(thingTypeUID)) {
ThingUID thingUID = new ThingUID(thingTypeUID, bridgeHandler.getThing().getUID(),
bridgeHandler.getThingName(device));
onDeviceAddedInternal(thingUID, device);
} else {
logger.debug("Discovered unsupported device: {}", device);
}
}
@Override
public void onDeviceUpdated(ThingUID thingUID, AVMFritzBaseModel device) {
onDeviceAddedInternal(thingUID, device);
}
@Override
public void onDeviceGone(ThingUID thingUID) {
// nothing to do
}
private void onDeviceAddedInternal(ThingUID thingUID, AVMFritzBaseModel device) {
if (device.getPresent() == 1) {
Map<String, Object> properties = new HashMap<>();
properties.put(CONFIG_AIN, device.getIdentifier());
properties.put(PROPERTY_VENDOR, device.getManufacturer());
properties.put(PROPERTY_MODEL_ID, device.getDeviceId());
properties.put(PROPERTY_SERIAL_NUMBER, device.getIdentifier());
properties.put(PROPERTY_FIRMWARE_VERSION, device.getFirmwareVersion());
if (device instanceof GroupModel && ((GroupModel) device).getGroupinfo() != null) {
properties.put(PROPERTY_MASTER, ((GroupModel) device).getGroupinfo().getMasterdeviceid());
properties.put(PROPERTY_MEMBERS, ((GroupModel) device).getGroupinfo().getMembers());
}
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
.withRepresentationProperty(CONFIG_AIN).withBridge(bridgeHandler.getThing().getUID())
.withLabel(device.getName()).build();
thingDiscovered(discoveryResult);
} else {
thingRemoved(thingUID);
}
}
}

View File

@@ -0,0 +1,127 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.discovery;
import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.*;
import static org.openhab.core.thing.Thing.PROPERTY_VENDOR;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.jupnp.model.meta.DeviceDetails;
import org.jupnp.model.meta.ModelDetails;
import org.jupnp.model.meta.RemoteDevice;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link AVMFritzUpnpDiscoveryParticipant} is responsible for discovering new and removed FRITZ!Box devices. It
* uses the central {@link UpnpDiscoveryService}.
*
* @author Robert Bausdorf - Initial contribution
* @author Christoph Weitkamp - Added support for groups
* @author Christoph Weitkamp - Use "discovery.avmfritz:background=false" to disable discovery service
*/
@Component(immediate = true, configurationPid = "discovery.avmfritz")
@NonNullByDefault
public class AVMFritzUpnpDiscoveryParticipant implements UpnpDiscoveryParticipant {
private final Logger logger = LoggerFactory.getLogger(AVMFritzUpnpDiscoveryParticipant.class);
private boolean isAutoDiscoveryEnabled = true;
@Activate
protected void activate(ComponentContext componentContext) {
activateOrModifyService(componentContext);
}
@Modified
protected void modified(ComponentContext componentContext) {
activateOrModifyService(componentContext);
}
private void activateOrModifyService(ComponentContext componentContext) {
Dictionary<String, @Nullable Object> properties = componentContext.getProperties();
String autoDiscoveryPropertyValue = (String) properties.get("background");
if (autoDiscoveryPropertyValue != null && autoDiscoveryPropertyValue.length() != 0) {
isAutoDiscoveryEnabled = Boolean.valueOf(autoDiscoveryPropertyValue);
}
}
@Override
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
return SUPPORTED_BRIDGE_THING_TYPES_UIDS;
}
@Override
public @Nullable DiscoveryResult createResult(RemoteDevice device) {
if (isAutoDiscoveryEnabled) {
ThingUID uid = getThingUID(device);
if (uid != null) {
logger.debug("discovered: {} ({}) at {}", device.getDisplayString(),
device.getDetails().getFriendlyName(), device.getIdentity().getDescriptorURL().getHost());
Map<String, Object> properties = new HashMap<>();
properties.put(CONFIG_IP_ADDRESS, device.getIdentity().getDescriptorURL().getHost());
properties.put(PROPERTY_VENDOR, device.getDetails().getManufacturerDetails().getManufacturer());
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties)
.withLabel(device.getDetails().getFriendlyName()).withRepresentationProperty(CONFIG_IP_ADDRESS)
.build();
return result;
}
}
return null;
}
@Override
public @Nullable ThingUID getThingUID(RemoteDevice device) {
// newer FRITZ!OS versions return several upnp services (e.g. Mediaserver)
if (device.getType().getType().equals(BRIDGE_FRITZBOX)) {
DeviceDetails details = device.getDetails();
if (details != null) {
ModelDetails modelDetails = details.getModelDetails();
if (modelDetails != null) {
String modelName = modelDetails.getModelName();
if (modelName != null) {
// It would be better to use udn but in my case FB is discovered twice
// .getIdentity().getUdn().getIdentifierString()
String id = device.getIdentity().getDescriptorURL().getHost().replaceAll(INVALID_PATTERN, "_");
if (modelName.startsWith(BOX_MODEL_NAME)) {
logger.debug("discovered on {}", device.getIdentity().getDiscoveredOnLocalAddress());
return new ThingUID(BRIDGE_THING_TYPE, id);
} else if (modelName.startsWith(POWERLINE_MODEL_NAME)) {
logger.debug("discovered on {}", device.getIdentity().getDiscoveredOnLocalAddress());
return new ThingUID(PL546E_STANDALONE_THING_TYPE, id);
}
}
}
}
}
return null;
}
}

View File

@@ -0,0 +1,219 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.dto;
import java.math.BigDecimal;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
/**
* See {@link DeviceListModel}.
*
* In the functionbitmask element value the following bits are used:
*
* <ol>
* <li>Bit 0: HAN-FUN Gerät</li>
* <li>Bit 3: Button</li>
* <li>Bit 4: Alarm-Sensor</li>
* <li>Bit 6: Comet DECT, Heizkörperregler</li>
* <li>Bit 7: Energie Messgerät</li>
* <li>Bit 8: Temperatursensor</li>
* <li>Bit 9: Schaltsteckdose</li>
* <li>Bit 10: AVM DECT Repeater</li>
* <li>Bit 11: Mikrofon</li>
* <li>Bit 13: HAN-FUN Unit</li>
* </ol>
*
* @author Robert Bausdorf - Initial contribution
* @author Christoph Weitkamp - Added support for AVM FRITZ!DECT 300 and Comet DECT
* @author Christoph Weitkamp - Added support for groups
*/
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 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
protected static final int OUTLET_BIT = 1 << 9; // Bit 9
protected static final int DECT_REPEATER_BIT = 1 << 10; // Bit 10
protected static final int MICROPHONE_BIT = 1 << 11; // Bit 11
protected static final int HAN_FUN_UNIT_BIT = 1 << 13; // Bit 13
@XmlAttribute(name = "identifier")
private String ident;
@XmlAttribute(name = "id")
private String deviceId;
@XmlAttribute(name = "functionbitmask")
private int bitmask;
@XmlAttribute(name = "fwversion")
private String firmwareVersion;
@XmlAttribute(name = "manufacturer")
private String deviceManufacturer;
@XmlAttribute(name = "productname")
private String productName;
@XmlElement(name = "present")
private Integer present;
@XmlElement(name = "name")
private String name;
@XmlElement(name = "battery")
private BigDecimal battery;
@XmlElement(name = "batterylow")
private BigDecimal batterylow;
@XmlElement(name = "switch")
private SwitchModel switchModel;
@XmlElement(name = "powermeter")
private PowerMeterModel powermeterModel;
@XmlElement(name = "hkr")
private HeatingModel heatingModel;
public PowerMeterModel getPowermeter() {
return powermeterModel;
}
public void setPowermeter(PowerMeterModel powermeter) {
this.powermeterModel = powermeter;
}
public HeatingModel getHkr() {
return heatingModel;
}
public void setHkr(HeatingModel heatingModel) {
this.heatingModel = heatingModel;
}
public SwitchModel getSwitch() {
return switchModel;
}
public void setSwitch(SwitchModel switchModel) {
this.switchModel = switchModel;
}
public String getIdentifier() {
return ident != null ? ident.replace(" ", "") : null;
}
public void setIdentifier(String identifier) {
this.ident = identifier;
}
public String getDeviceId() {
return deviceId;
}
public boolean isHANFUNDevice() {
return (bitmask & HAN_FUN_DEVICE_BIT) > 0;
}
public boolean isHANFUNButton() {
return (bitmask & HAN_FUN_BUTTON_BIT) > 0;
}
public boolean isHANFUNAlarmSensor() {
return (bitmask & HAN_FUN_ALARM_SENSOR_BIT) > 0;
}
public boolean isButton() {
return (bitmask & BUTTON_BIT) > 0;
}
public boolean isSwitchableOutlet() {
return (bitmask & OUTLET_BIT) > 0;
}
public boolean isTempSensor() {
return (bitmask & TEMPSENSOR_BIT) > 0;
}
public boolean isPowermeter() {
return (bitmask & POWERMETER_BIT) > 0;
}
public boolean isDectRepeater() {
return (bitmask & DECT_REPEATER_BIT) > 0;
}
public boolean isHeatingThermostat() {
return (bitmask & HEATING_THERMOSTAT_BIT) > 0;
}
public boolean isMicrophone() {
return (bitmask & MICROPHONE_BIT) > 0;
}
public boolean isHANFUNUnit() {
return (bitmask & HAN_FUN_UNIT_BIT) > 0;
}
public String getFirmwareVersion() {
return firmwareVersion;
}
public String getManufacturer() {
return deviceManufacturer;
}
public String getProductName() {
return productName;
}
public int getPresent() {
return present;
}
public String getName() {
return name;
}
@Override
public BigDecimal getBattery() {
return battery;
}
@Override
public BigDecimal getBatterylow() {
return batterylow;
}
@Override
public String toString() {
return new StringBuilder().append("[ain=").append(ident).append(",bitmask=").append(bitmask)
.append(",isHANFUNDevice=").append(isHANFUNDevice()).append(",isHANFUNButton=").append(isHANFUNButton())
.append(",isHANFUNAlarmSensor=").append(isHANFUNAlarmSensor()).append(",isButton").append(isButton())
.append(",isSwitchableOutlet=").append(isSwitchableOutlet()).append(",isTempSensor=")
.append(isTempSensor()).append(",isPowermeter=").append(isPowermeter()).append(",isDectRepeater=")
.append(isDectRepeater()).append(",isHeatingThermostat=").append(isHeatingThermostat())
.append(",isMicrophone=").append(isMicrophone()).append(",isHANFUNUnit=").append(isHANFUNUnit())
.append(",id=").append(deviceId).append(",manufacturer=").append(deviceManufacturer)
.append(",productname=").append(productName).append(",fwversion=").append(firmwareVersion)
.append(",present=").append(present).append(",name=").append(name).append(",battery")
.append(getBattery()).append(",batterylow").append(getBatterylow()).append(getSwitch())
.append(getPowermeter()).append(getHkr()).toString();
}
}

View File

@@ -0,0 +1,48 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.dto;
import java.math.BigDecimal;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* See {@link DeviceListModel}.
*
* @author Christoph Weitkamp - Initial contribution
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = { "state" })
@XmlRootElement(name = "alert")
public class AlertModel {
public static final BigDecimal ON = BigDecimal.ONE;
public static final BigDecimal OFF = BigDecimal.ZERO;
private BigDecimal state;
public BigDecimal getState() {
return state;
}
public void setState(BigDecimal state) {
this.state = state;
}
@Override
public String toString() {
return new StringBuilder().append("[state=").append(state).append("]").toString();
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.dto;
import java.math.BigDecimal;
/**
* See {@link AVMFritzBaseModel} -> {@link DeviceModel} and {@link HeatingModel}.
*
* @author Christoph Weitkamp - Initial contribution
*/
public interface BatteryModel {
public static final BigDecimal BATTERY_OFF = BigDecimal.ZERO;
public static final BigDecimal BATTERY_ON = BigDecimal.ONE;
BigDecimal getBattery();
BigDecimal getBatterylow();
}

View File

@@ -0,0 +1,107 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.dto;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
* See {@link DeviceListModel}.
*
* @author Christoph Weitkamp - Initial contribution
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "button")
public class ButtonModel {
@XmlAttribute(name = "identifier")
private String identifier;
@XmlAttribute(name = "id")
private String buttonId;
@XmlElement(name = "name")
private String name;
@XmlElement(name = "lastpressedtimestamp")
private int lastpressedtimestamp;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getIdentifier() {
return identifier != null ? identifier.replace(" ", "") : null;
}
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
public String getButtonId() {
return buttonId;
}
public void setButtonId(String buttonId) {
this.buttonId = buttonId;
}
public int getLastpressedtimestamp() {
return lastpressedtimestamp;
}
public void setLastpressedtimestamp(int lastpressedtimestamp) {
this.lastpressedtimestamp = lastpressedtimestamp;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (identifier != null ? identifier.hashCode() : 0);
result = prime * result + (buttonId != null ? buttonId.hashCode() : 0);
result = prime * result + (name != null ? name.hashCode() : 0);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ButtonModel other = (ButtonModel) obj;
return (identifier != null ? identifier.equals(other.identifier) : other.identifier == null) && //
(buttonId != null ? buttonId.equals(other.buttonId) : other.buttonId == null) && //
(name != null ? name.equals(other.name) : other.name == null);
}
@Override
public String toString() {
return new StringBuilder().append("[identifier=").append(getIdentifier()).append(",id=").append(buttonId)
.append(",name=").append(name).append(",lastpressedtimestamp=").append(lastpressedtimestamp).append("]")
.toString();
}
}

View File

@@ -0,0 +1,118 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.dto;
import java.util.Collections;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElements;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* This JAXB model class maps the XML response to an <b>getdevicelistinfos</b>
* command on a FRITZ!Box device. As of today, this class is able to to bind the
* devicelist version 1 (currently used by AVM) response:
*
* <pre>
* <devicelist version="1">
* <device identifier="##############" id="##" functionbitmask="2944" fwversion="03.83" manufacturer="AVM" productname=
* "FRITZ!DECT 200">
* <present>1</present>
* <name>FRITZ!DECT 200 #1</name>
* <switch>
* <state>0</state>
* <mode>manuell</mode>
* <lock>0</lock>
* <devicelock>1</devicelock>
* </switch>
* <powermeter>
* <power>0</power>
* <energy>166</energy>
* </powermeter>
* <temperature>
* <celsius>255</celsius>
* <offset>0</offset>
* </temperature>
* </device>
* <device identifier="##############" id="xx" functionbitmask="320" fwversion="03.50" manufacturer="AVM" productname=
* "Comet DECT">
* <present>1</present>
* <name>Comet DECT #1</name>
* <temperature>
* <celsius>220</celsius>
* <offset>-10</offset>
* </temperature>
* <hkr>
* <tist>44</tist>
* <tsoll>42</tsoll>
* <absenk>28</absenk>
* <komfort>42</komfort>
* <lock>0</lock>
* <devicelock>0</devicelock>
* <errorcode>0</errorcode>
* <batterylow>0</batterylow>
* <nextchange>
* <endperiod>1484341200</endperiod>
* <tchange>28</tchange>
* </nextchange>
* </hkr>
* </device>
* </devicelist>
*
* <pre>
*
* @author Robert Bausdorf - Initial contribution
* @author Christoph Weitkamp - Added support for groups
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType
@XmlRootElement(name = "devicelist")
public class DeviceListModel {
@XmlAttribute(name = "version")
private String apiVersion;
//@formatter:off
@XmlElements({
@XmlElement(name = "device", type = DeviceModel.class),
@XmlElement(name = "group", type = GroupModel.class)
})
//@formatter:on
private List<AVMFritzBaseModel> devices;
public List<AVMFritzBaseModel> getDevicelist() {
if (devices == null) {
devices = Collections.emptyList();
}
return devices;
}
public void setDevicelist(List<AVMFritzBaseModel> devices) {
this.devices = devices;
}
public String getXmlApiVersion() {
return apiVersion;
}
@Override
public String toString() {
return new StringBuilder().append("[devices=").append(devices).append(",version=").append(apiVersion)
.append("]").toString();
}
}

View File

@@ -0,0 +1,133 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.dto;
import java.util.Collections;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
/**
* See {@link AVMFritzBaseModel}.
*
* @author Robert Bausdorf - Initial contribution
* @author Christoph Weitkamp - Added support for groups
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "device")
public class DeviceModel extends AVMFritzBaseModel {
private TemperatureModel temperature;
private AlertModel alert;
@XmlElement(name = "button", type = ButtonModel.class)
private List<ButtonModel> buttons;
private ETSUnitInfoModel etsiunitinfo;
public TemperatureModel getTemperature() {
return temperature;
}
public void setTemperature(TemperatureModel temperatureModel) {
this.temperature = temperatureModel;
}
public AlertModel getAlert() {
return alert;
}
public void setAlert(AlertModel alertModel) {
this.alert = alertModel;
}
public List<ButtonModel> getButtons() {
if (buttons == null) {
return Collections.emptyList();
}
return buttons;
}
public void setButtons(List<ButtonModel> buttons) {
this.buttons = buttons;
}
public ETSUnitInfoModel getEtsiunitinfo() {
return etsiunitinfo;
}
public void setEtsiunitinfo(ETSUnitInfoModel etsiunitinfo) {
this.etsiunitinfo = etsiunitinfo;
}
@Override
public String toString() {
return new StringBuilder().append(super.toString()).append(temperature).append(alert).append(getButtons())
.append(etsiunitinfo).append("]").toString();
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = { "etsideviceid", "unittype", "interfaces" })
public static class ETSUnitInfoModel {
public static final String HAN_FUN_UNITTYPE_SIMPLE_BUTTON = "273";
public static final String HAN_FUN_UNITTYPE_SIMPLE_DETECTOR = "512";
public static final String HAN_FUN_UNITTYPE_MAGNETIC_CONTACT = "513";
public static final String HAN_FUN_UNITTYPE_OPTICAL_CONTACT = "514";
public static final String HAN_FUN_UNITTYPE_MOTION_DETECTOR = "515";
public static final String HAN_FUN_UNITTYPE_SMOKE_DETECTOR = "516";
public static final String HAN_FUN_UNITTYPE_FLOOD_DETECTOR = "518";
public static final String HAN_FUN_UNITTYPE_GLAS_BREAK_DETECTOR = "519";
public static final String HAN_FUN_UNITTYPE_VIBRATION_DETECTOR = "520";
public static final String HAN_FUN_INTERFACE_ALERT = "256";
public static final String HAN_FUN_INTERFACE_KEEP_ALIVE = "277";
public static final String HAN_FUN_INTERFACE_SIMPLE_BUTTON = "772";
private String etsideviceid;
private String unittype;
private String interfaces;
public String getEtsideviceid() {
return etsideviceid;
}
public void setEtsideviceid(String etsideviceid) {
this.etsideviceid = etsideviceid;
}
public String getUnittype() {
return unittype;
}
public void setUnittype(String unittype) {
this.unittype = unittype;
}
public String getInterfaces() {
return interfaces;
}
public void setInterfaces(String interfaces) {
this.interfaces = interfaces;
}
@Override
public String toString() {
return new StringBuilder().append("[etsideviceid=").append(etsideviceid).append(",unittype=")
.append(unittype).append(",interfaces=").append(interfaces).append("]").toString();
}
}
}

View File

@@ -0,0 +1,71 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.dto;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlType;
/**
* See {@link AVMFritzBaseModel}.
*
* @author Christoph Weitkamp - Initial contribution
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "group")
public class GroupModel extends AVMFritzBaseModel {
private GroupInfoModel groupinfo;
public GroupInfoModel getGroupinfo() {
return groupinfo;
}
public void setGroupinfo(GroupInfoModel groupinfo) {
this.groupinfo = groupinfo;
}
@Override
public String toString() {
return new StringBuilder().append(super.toString()).append(groupinfo).append("]").toString();
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = { "masterdeviceid", "members" })
public static class GroupInfoModel {
private String masterdeviceid;
private String members;
public String getMasterdeviceid() {
return masterdeviceid;
}
public void setMasterdeviceid(String masterdeviceid) {
this.masterdeviceid = masterdeviceid;
}
public String getMembers() {
return members;
}
public void setMembers(String members) {
this.members = members;
}
@Override
public String toString() {
return new StringBuilder().append("[masterdeviceid=").append(masterdeviceid).append(",members=")
.append(members).append("]").toString();
}
}
}

View File

@@ -0,0 +1,300 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.dto;
import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import org.eclipse.jdt.annotation.Nullable;
/**
* See {@link DeviceListModel}.
*
* @author Christoph Weitkamp - Initial contribution
* @author Christoph Weitkamp - Added support for AVM FRITZ!DECT 300 and Comet DECT
* @author Christoph Weitkamp - Added channel 'battery_level'
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "hkr")
public class HeatingModel implements BatteryModel {
public static final BigDecimal TEMP_FACTOR = new BigDecimal("0.5");
public static final BigDecimal BIG_DECIMAL_TWO = new BigDecimal("2.0");
public static final BigDecimal TEMP_CELSIUS_MIN = new BigDecimal("8.0");
public static final BigDecimal TEMP_CELSIUS_MAX = new BigDecimal("28.0");
public static final BigDecimal TEMP_FRITZ_MIN = new BigDecimal("16.0");
public static final BigDecimal TEMP_FRITZ_MAX = new BigDecimal("56.0");
public static final BigDecimal TEMP_FRITZ_OFF = new BigDecimal("253.0");
public static final BigDecimal TEMP_FRITZ_ON = new BigDecimal("254.0");
public static final BigDecimal TEMP_FRITZ_UNDEFINED = new BigDecimal("255.0");
private BigDecimal tist;
private BigDecimal tsoll;
private BigDecimal absenk;
private BigDecimal komfort;
private BigDecimal lock;
private BigDecimal devicelock;
private String errorcode;
private BigDecimal batterylow;
private @Nullable BigDecimal windowopenactiv;
private @Nullable BigDecimal windowopenactiveendtime;
private @Nullable BigDecimal boostactive;
private @Nullable BigDecimal boostactiveendtime;
private BigDecimal battery;
private NextChangeModel nextchange;
private BigDecimal summeractive;
private BigDecimal holidayactive;
public BigDecimal getTist() {
return tist;
}
public void setTist(BigDecimal tist) {
this.tist = tist;
}
public BigDecimal getTsoll() {
return tsoll;
}
public void setTsoll(BigDecimal tsoll) {
this.tsoll = tsoll;
}
public BigDecimal getKomfort() {
return komfort;
}
public void setKomfort(BigDecimal komfort) {
this.komfort = komfort;
}
public BigDecimal getAbsenk() {
return absenk;
}
public void setAbsenk(BigDecimal absenk) {
this.absenk = absenk;
}
public String getMode() {
if (BigDecimal.ONE.equals(getHolidayactive())) {
return MODE_VACATION;
} else if (getNextchange() != null && getNextchange().getEndperiod() != 0) {
return MODE_AUTO;
} else {
return MODE_MANUAL;
}
}
public String getRadiatorMode() {
if (tsoll == null) {
return MODE_UNKNOWN;
} else if (TEMP_FRITZ_ON.compareTo(tsoll) == 0) {
return MODE_ON;
} else if (TEMP_FRITZ_OFF.compareTo(tsoll) == 0) {
return MODE_OFF;
} else if (BigDecimal.ONE.equals(getWindowopenactiv())) {
return MODE_WINDOW_OPEN;
} else if (tsoll.compareTo(komfort) == 0) {
return MODE_COMFORT;
} else if (tsoll.compareTo(absenk) == 0) {
return MODE_ECO;
} else if (BigDecimal.ONE.equals(getBoostactive()) || TEMP_FRITZ_MAX.compareTo(tsoll) == 0) {
return MODE_BOOST;
} else {
return MODE_ON;
}
}
public BigDecimal getLock() {
return lock;
}
public void setLock(BigDecimal lock) {
this.lock = lock;
}
public BigDecimal getDevicelock() {
return devicelock;
}
public void setDevicelock(BigDecimal devicelock) {
this.devicelock = devicelock;
}
public String getErrorcode() {
return errorcode;
}
public void setErrorcode(String errorcode) {
this.errorcode = errorcode;
}
@Override
public BigDecimal getBatterylow() {
return batterylow;
}
public void setBatterylow(BigDecimal batterylow) {
this.batterylow = batterylow;
}
public @Nullable BigDecimal getWindowopenactiv() {
return windowopenactiv;
}
public @Nullable BigDecimal getWindowopenactiveendtime() {
return windowopenactiveendtime;
}
public void setWindowopenactiv(BigDecimal windowopenactiv) {
this.windowopenactiv = windowopenactiv;
}
public @Nullable BigDecimal getBoostactive() {
return boostactive;
}
public @Nullable BigDecimal getBoostactiveendtime() {
return boostactiveendtime;
}
@Override
public BigDecimal getBattery() {
return battery;
}
public void setBattery(BigDecimal battery) {
this.battery = battery;
}
public NextChangeModel getNextchange() {
return nextchange;
}
public void setNextchange(NextChangeModel nextchange) {
this.nextchange = nextchange;
}
public BigDecimal getSummeractive() {
return summeractive;
}
public void setSummeractive(BigDecimal summeractive) {
this.summeractive = summeractive;
}
public BigDecimal getHolidayactive() {
return holidayactive;
}
public void setHolidayactive(BigDecimal holidayactive) {
this.holidayactive = holidayactive;
}
@Override
public String toString() {
return new StringBuilder().append("[tist=").append(tist).append(",tsoll=").append(tsoll).append(",absenk=")
.append(absenk).append(",komfort=").append(komfort).append(",lock=").append(lock).append(",devicelock=")
.append(devicelock).append(",errorcode=").append(errorcode).append(",batterylow=").append(batterylow)
.append(",windowopenactiv=").append(windowopenactiv).append(",windowopenactiveendtime=")
.append(windowopenactiveendtime).append(",boostactive=").append(boostactive)
.append(",boostactiveendtime=").append(boostactiveendtime).append(",battery=").append(battery)
.append(",nextchange=").append(nextchange).append(",summeractive=").append(summeractive)
.append(",holidayactive=").append(holidayactive).append("]").toString();
}
/**
* Converts a celsius value to a FRITZ!Box value.
* Valid celsius values: 8 to 28 °C > 16 to 56
* 16 <= 8°C, 17 = 8.5°C...... 56 >= 28°C, 254 = ON, 253 = OFF
*
* @param celsiusValue The celsius value to be converted
* @return The FRITZ!Box value
*/
public static BigDecimal fromCelsius(BigDecimal celsiusValue) {
if (celsiusValue == null) {
return BigDecimal.ZERO;
} else if (TEMP_CELSIUS_MIN.compareTo(celsiusValue) == 1) {
return TEMP_FRITZ_MIN;
} else if (TEMP_CELSIUS_MAX.compareTo(celsiusValue) == -1) {
return TEMP_FRITZ_MAX;
}
return BIG_DECIMAL_TWO.multiply(celsiusValue);
}
/**
* Converts a celsius value to a FRITZ!Box value.
* Valid celsius values: 8 to 28 °C > 16 to 56
* 16 <= 8°C, 17 = 8.5°C...... 56 >= 28°C, 254 = ON, 253 = OFF
*
* @param celsiusValue The celsius value to be converted
* @return The FRITZ!Box value
*/
public static BigDecimal toCelsius(BigDecimal fritzValue) {
if (fritzValue == null) {
return BigDecimal.ZERO;
} else if (TEMP_FRITZ_ON.compareTo(fritzValue) == 0) {
return TEMP_CELSIUS_MAX.add(BIG_DECIMAL_TWO);
} else if (TEMP_FRITZ_OFF.compareTo(fritzValue) == 0) {
return TEMP_CELSIUS_MIN.subtract(BIG_DECIMAL_TWO);
}
return TEMP_FACTOR.multiply(fritzValue);
}
/**
* Normalizes a celsius value.
* Valid celsius steps: 0.5°C
*
* @param celsiusValue The celsius value to be normalized
* @return The normalized celsius value
*/
public static BigDecimal normalizeCelsius(BigDecimal celsiusValue) {
BigDecimal divisor = celsiusValue.divide(TEMP_FACTOR, 0, RoundingMode.HALF_UP);
return TEMP_FACTOR.multiply(divisor);
}
@XmlAccessorType(XmlAccessType.FIELD)
public static class NextChangeModel {
private int endperiod;
private BigDecimal tchange;
public int getEndperiod() {
return endperiod;
}
public void setEndperiod(int endperiod) {
this.endperiod = endperiod;
}
public BigDecimal getTchange() {
return tchange;
}
public void setTchange(BigDecimal tchange) {
this.tchange = tchange;
}
@Override
public String toString() {
return new StringBuilder().append("[endperiod=").append(endperiod).append(",tchange=").append(tchange)
.append("]").toString();
}
}
}

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.dto;
import java.math.BigDecimal;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* See {@link DeviceListModel}.
*
* @author Robert Bausdorf - Initial contribution
* @author Christoph Weitkamp - Added channel 'voltage'
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = { "voltage", "power", "energy" })
@XmlRootElement(name = "powermeter")
public class PowerMeterModel {
public static final BigDecimal VOLTAGE_FACTOR = new BigDecimal("0.001");
public static final BigDecimal POWER_FACTOR = new BigDecimal("0.001");
private BigDecimal voltage;
private BigDecimal power;
private BigDecimal energy;
public BigDecimal getVoltage() {
return voltage != null ? VOLTAGE_FACTOR.multiply(voltage) : BigDecimal.ZERO;
}
public void setVoltage(BigDecimal voltage) {
this.voltage = voltage;
}
public BigDecimal getPower() {
return power != null ? POWER_FACTOR.multiply(power) : BigDecimal.ZERO;
}
public void setPower(BigDecimal power) {
this.power = power;
}
public BigDecimal getEnergy() {
return energy != null ? energy : BigDecimal.ZERO;
}
public void setEnergy(BigDecimal energy) {
this.energy = energy;
}
@Override
public String toString() {
return new StringBuilder().append("[voltage=").append(getVoltage()).append(",power=").append(getPower())
.append(",energy=").append(getEnergy()).append("]").toString();
}
}

View File

@@ -0,0 +1,86 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.dto;
import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.*;
import java.math.BigDecimal;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* See {@link DeviceListModel}.
*
* @author Robert Bausdorf - Initial contribution
* @author Christoph Weitkamp - Added new channels `locked`, `mode` and `radiator_mode`
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = { "state", "mode", "lock", "devicelock" })
@XmlRootElement(name = "switch")
public class SwitchModel {
public static final BigDecimal ON = BigDecimal.ONE;
public static final BigDecimal OFF = BigDecimal.ZERO;
public static final String MODE_FRITZ_AUTO = "auto";
public static final String MODE_FRITZ_MANUAL = "manuell";
private BigDecimal state;
private String mode;
private BigDecimal lock;
private BigDecimal devicelock;
public BigDecimal getState() {
return state;
}
public void setState(BigDecimal state) {
this.state = state;
}
public String getMode() {
if (MODE_FRITZ_AUTO.equals(mode)) {
return MODE_AUTO;
} else {
return MODE_MANUAL;
}
}
public void setMode(String mode) {
this.mode = mode;
}
public BigDecimal getLock() {
return lock;
}
public void setLock(BigDecimal lock) {
this.lock = lock;
}
public BigDecimal getDevicelock() {
return devicelock;
}
public void setDevicelock(BigDecimal devicelock) {
this.devicelock = devicelock;
}
@Override
public String toString() {
return new StringBuilder().append("[state=").append(state).append(",mode=").append(getMode()).append(",lock=")
.append(lock).append(",devicelock=").append(devicelock).append("]").toString();
}
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.dto;
import java.math.BigDecimal;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* See {@link DeviceListModel}.
*
* @author Robert Bausdorf - Initial contribution
* @author Christoph Weitkamp - Refactoring of temperature conversion from celsius to FRITZ!Box values
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = { "celsius", "offset" })
@XmlRootElement(name = "temperature")
public class TemperatureModel {
public static final BigDecimal TEMP_FACTOR = new BigDecimal("0.1");
private BigDecimal celsius;
private BigDecimal offset;
public BigDecimal getCelsius() {
return celsius != null ? TEMP_FACTOR.multiply(celsius) : BigDecimal.ZERO;
}
public void setCelsius(BigDecimal celsius) {
this.celsius = celsius;
}
public BigDecimal getOffset() {
return offset != null ? TEMP_FACTOR.multiply(offset) : BigDecimal.ZERO;
}
public void setOffset(BigDecimal offset) {
this.offset = offset;
}
@Override
public String toString() {
return new StringBuilder().append("[celsius=").append(getCelsius()).append(",offset=").append(getOffset())
.append("]").toString();
}
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.dto.templates;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
/**
* See {@ TemplateModel}.
*
* @author Christoph Weitkamp - Initial contribution
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "applymask")
public class ApplyMaskListModel {
@Override
public String toString() {
return new StringBuilder().append("[]").toString();
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.dto.templates;
import java.util.Collections;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
* See {@ TemplateModel}.
*
* @author Christoph Weitkamp - Initial contribution
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "devices")
public class DeviceListModel {
@XmlElement(name = "device")
private List<DeviceModel> devices;
public List<DeviceModel> getDevices() {
if (devices == null) {
devices = Collections.emptyList();
}
return devices;
}
@Override
public String toString() {
return new StringBuilder().append("[devices=").append(devices).append("]").toString();
}
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.dto.templates;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;
/**
* See {@link DeviceListModel}.
*
* @author Christoph Weitkamp - Initial contribution
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "device")
public class DeviceModel {
@XmlAttribute(name = "identifier")
private String identifier;
public String getIdentifier() {
return identifier != null ? identifier.replace(" ", "") : null;
}
@Override
public String toString() {
return new StringBuilder().append("[identifier=").append(identifier).append("]").toString();
}
}

View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.dto.templates;
import java.util.Collections;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* This JAXB model class maps the XML response to an <b>gettemplatelistinfos</b> command on a FRITZ!Box device.
*
* @author Christoph Weitkamp - Initial contribution
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType
@XmlRootElement(name = "templatelist")
public class TemplateListModel {
@XmlAttribute(name = "version")
private String version;
@XmlElement(name = "template")
private List<TemplateModel> templates;
public List<TemplateModel> getTemplates() {
if (templates == null) {
templates = Collections.emptyList();
}
return templates;
}
public String getVersion() {
return version;
}
@Override
public String toString() {
return new StringBuilder().append("[templates=").append(templates).append(",version=").append(version)
.append("]").toString();
}
}

View File

@@ -0,0 +1,84 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.dto.templates;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import org.openhab.core.types.CommandOption;
/**
* See {@link TemplateListModel}.
*
* @author Christoph Weitkamp - Initial contribution
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "template")
public class TemplateModel {
@XmlAttribute(name = "identifier")
private String identifier;
@XmlAttribute(name = "id")
private String templateId;
@XmlAttribute(name = "functionbitmask")
private int functionbitmask;
@XmlAttribute(name = "applymask")
private int applymask;
@XmlElement(name = "name")
private String name;
@XmlElement(name = "devices")
private DeviceListModel deviceList;
@XmlElement(name = "applymask")
private ApplyMaskListModel applyMaskList;
public String getIdentifier() {
return identifier != null ? identifier.replace(" ", "") : null;
}
public String getTemplateId() {
return templateId;
}
public String getName() {
return name;
}
public DeviceListModel getDeviceList() {
return deviceList;
}
public ApplyMaskListModel getApplyMaskList() {
return applyMaskList;
}
public CommandOption toCommandOption() {
return new CommandOption(getIdentifier(), getName());
}
@Override
public String toString() {
return new StringBuilder().append("[identifier=").append(identifier).append(",id=").append(templateId)
.append(",functionbitmask=").append(functionbitmask).append(",applymask=").append(applymask)
.append(",name=").append(name).append(",devices=").append(deviceList).append(",applymasks=")
.append(applyMaskList).append("]").toString();
}
}

View File

@@ -0,0 +1,389 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.handler;
import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.*;
import static org.openhab.binding.avmfritz.internal.dto.DeviceModel.ETSUnitInfoModel.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants;
import org.openhab.binding.avmfritz.internal.AVMFritzDynamicCommandDescriptionProvider;
import org.openhab.binding.avmfritz.internal.config.AVMFritzBoxConfiguration;
import org.openhab.binding.avmfritz.internal.discovery.AVMFritzDiscoveryService;
import org.openhab.binding.avmfritz.internal.dto.AVMFritzBaseModel;
import org.openhab.binding.avmfritz.internal.dto.DeviceModel;
import org.openhab.binding.avmfritz.internal.dto.GroupModel;
import org.openhab.binding.avmfritz.internal.dto.templates.TemplateModel;
import org.openhab.binding.avmfritz.internal.hardware.FritzAhaStatusListener;
import org.openhab.binding.avmfritz.internal.hardware.FritzAhaWebInterface;
import org.openhab.binding.avmfritz.internal.hardware.callbacks.FritzAhaApplyTemplateCallback;
import org.openhab.binding.avmfritz.internal.hardware.callbacks.FritzAhaUpdateCallback;
import org.openhab.binding.avmfritz.internal.hardware.callbacks.FritzAhaUpdateTemplatesCallback;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract handler for a FRITZ! bridge. Handles polling of values from AHA devices.
*
* @author Robert Bausdorf - Initial contribution
* @author Christoph Weitkamp - Added support for AVM FRITZ!DECT 300 and Comet DECT
* @author Christoph Weitkamp - Added support for groups
*/
@NonNullByDefault
public abstract class AVMFritzBaseBridgeHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(AVMFritzBaseBridgeHandler.class);
/**
* Initial delay in s for polling job.
*/
private static final int INITIAL_DELAY = 1;
/**
* Refresh interval which is used to poll values from the FRITZ!Box web interface (optional, defaults to 15 s)
*/
private long refreshInterval = 15;
/**
* Interface object for querying the FRITZ!Box web interface
*/
protected @Nullable FritzAhaWebInterface connection;
/**
* Schedule for polling
*/
private @Nullable ScheduledFuture<?> pollingJob;
/**
* Shared instance of HTTP client for asynchronous calls
*/
protected final HttpClient httpClient;
private final AVMFritzDynamicCommandDescriptionProvider commandDescriptionProvider;
protected final List<FritzAhaStatusListener> listeners = new CopyOnWriteArrayList<>();
/**
* keeps track of the {@link ChannelUID} for the 'apply_template' {@link Channel}
*/
private final ChannelUID applyTemplateChannelUID;
/**
* Constructor
*
* @param bridge Bridge object representing a FRITZ!Box
*/
public AVMFritzBaseBridgeHandler(Bridge bridge, HttpClient httpClient,
AVMFritzDynamicCommandDescriptionProvider commandDescriptionProvider) {
super(bridge);
this.httpClient = httpClient;
this.commandDescriptionProvider = commandDescriptionProvider;
applyTemplateChannelUID = new ChannelUID(bridge.getUID(), CHANNEL_APPLY_TEMPLATE);
}
@Override
public void initialize() {
boolean configValid = true;
AVMFritzBoxConfiguration config = getConfigAs(AVMFritzBoxConfiguration.class);
String localIpAddress = config.ipAddress;
if (localIpAddress == null || localIpAddress.trim().isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"The 'ipAddress' parameter must be configured.");
configValid = false;
}
refreshInterval = config.pollingInterval;
if (refreshInterval < 5) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"The 'pollingInterval' parameter must be greater then at least 5 seconds.");
configValid = false;
}
if (configValid) {
updateStatus(ThingStatus.UNKNOWN);
manageConnections();
}
}
protected synchronized void manageConnections() {
AVMFritzBoxConfiguration config = getConfigAs(AVMFritzBoxConfiguration.class);
if (this.connection == null) {
this.connection = new FritzAhaWebInterface(config, this, httpClient);
stopPolling();
startPolling();
}
}
@Override
public void channelLinked(ChannelUID channelUID) {
manageConnections();
super.channelLinked(channelUID);
}
@Override
public void channelUnlinked(ChannelUID channelUID) {
manageConnections();
super.channelUnlinked(channelUID);
}
@Override
public void dispose() {
stopPolling();
}
@Override
public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
if (childHandler instanceof FritzAhaStatusListener) {
registerStatusListener((FritzAhaStatusListener) childHandler);
}
}
@Override
public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
if (childHandler instanceof FritzAhaStatusListener) {
unregisterStatusListener((FritzAhaStatusListener) childHandler);
}
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(AVMFritzDiscoveryService.class);
}
public boolean registerStatusListener(FritzAhaStatusListener listener) {
return listeners.add(listener);
}
public boolean unregisterStatusListener(FritzAhaStatusListener listener) {
return listeners.remove(listener);
}
/**
* Start the polling.
*/
protected void startPolling() {
ScheduledFuture<?> localPollingJob = pollingJob;
if (localPollingJob == null || localPollingJob.isCancelled()) {
logger.debug("Start polling job at interval {}s", refreshInterval);
pollingJob = scheduler.scheduleWithFixedDelay(this::poll, INITIAL_DELAY, refreshInterval, TimeUnit.SECONDS);
}
}
/**
* Stops the polling.
*/
protected void stopPolling() {
ScheduledFuture<?> localPollingJob = pollingJob;
if (localPollingJob != null && !localPollingJob.isCancelled()) {
logger.debug("Stop polling job");
localPollingJob.cancel(true);
pollingJob = null;
}
}
/**
* Polls the bridge.
*/
private void poll() {
FritzAhaWebInterface webInterface = getWebInterface();
if (webInterface != null) {
logger.debug("Poll FRITZ!Box for updates {}", thing.getUID());
FritzAhaUpdateCallback updateCallback = new FritzAhaUpdateCallback(webInterface, this);
webInterface.asyncGet(updateCallback);
if (isLinked(applyTemplateChannelUID)) {
logger.debug("Poll FRITZ!Box for templates {}", thing.getUID());
FritzAhaUpdateTemplatesCallback templateCallback = new FritzAhaUpdateTemplatesCallback(webInterface,
this);
webInterface.asyncGet(templateCallback);
}
}
}
/**
* Called from {@link FritzAhaWebInterface#authenticate()} to update the bridge status because updateStatus is
* protected.
*
* @param status Bridge status
* @param statusDetail Bridge status detail
* @param description Bridge status description
*/
public void setStatusInfo(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
updateStatus(status, statusDetail, description);
}
/**
* Called from {@link FritzAhaApplyTemplateCallback} to provide new templates for things.
*
* @param templateList list of template models
*/
public void addTemplateList(List<TemplateModel> templateList) {
commandDescriptionProvider.setCommandOptions(applyTemplateChannelUID,
templateList.stream().map(TemplateModel::toCommandOption).collect(Collectors.toList()));
}
/**
* Called from {@link FritzAhaUpdateCallback} to provide new devices.
*
* @param deviceList list of devices
*/
public void onDeviceListAdded(List<AVMFritzBaseModel> deviceList) {
final Map<String, AVMFritzBaseModel> deviceIdentifierMap = deviceList.stream()
.collect(Collectors.toMap(it -> it.getIdentifier(), Function.identity()));
getThing().getThings().forEach(childThing -> {
final AVMFritzBaseThingHandler childHandler = (AVMFritzBaseThingHandler) childThing.getHandler();
if (childHandler != null) {
final Optional<AVMFritzBaseModel> optionalDevice = Optional
.ofNullable(deviceIdentifierMap.get(childHandler.getIdentifier()));
if (optionalDevice.isPresent()) {
final AVMFritzBaseModel device = optionalDevice.get();
deviceList.remove(device);
listeners.forEach(listener -> listener.onDeviceUpdated(childThing.getUID(), device));
} else {
listeners.forEach(listener -> listener.onDeviceGone(childThing.getUID()));
}
} else {
logger.debug("Handler missing for thing '{}'", childThing.getUID());
}
});
deviceList.forEach(device -> {
listeners.forEach(listener -> listener.onDeviceAdded(device));
});
}
/**
* Builds a {@link ThingUID} from a device model. The UID is build from the
* {@link AVMFritzBindingConstants#BINDING_ID} and
* value of {@link AVMFritzBaseModel#getProductName()} in which all characters NOT matching the RegEx [^a-zA-Z0-9_]
* are replaced by "_".
*
* @param device Discovered device model
* @return ThingUID without illegal characters.
*/
public @Nullable ThingUID getThingUID(AVMFritzBaseModel device) {
ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, getThingTypeId(device));
ThingUID bridgeUID = thing.getUID();
String thingName = getThingName(device);
if (SUPPORTED_BUTTON_THING_TYPES_UIDS.contains(thingTypeUID)
|| SUPPORTED_HEATING_THING_TYPES.contains(thingTypeUID)
|| SUPPORTED_DEVICE_THING_TYPES_UIDS.contains(thingTypeUID)) {
return new ThingUID(thingTypeUID, bridgeUID, thingName);
} else if (device.isHeatingThermostat()) {
return new ThingUID(GROUP_HEATING_THING_TYPE, bridgeUID, thingName);
} else if (device.isSwitchableOutlet()) {
return new ThingUID(GROUP_SWITCH_THING_TYPE, bridgeUID, thingName);
} else {
return null;
}
}
/**
*
* @param device Discovered device model
* @return ThingTypeId without illegal characters.
*/
public String getThingTypeId(AVMFritzBaseModel device) {
if (device instanceof GroupModel) {
if (device.isHeatingThermostat()) {
return GROUP_HEATING;
} else if (device.isSwitchableOutlet()) {
return GROUP_SWITCH;
}
} else if (device instanceof DeviceModel && device.isHANFUNUnit()) {
List<String> interfaces = Arrays
.asList(((DeviceModel) device).getEtsiunitinfo().getInterfaces().split(","));
if (interfaces.contains(HAN_FUN_INTERFACE_ALERT)) {
return DEVICE_HAN_FUN_CONTACT;
} else if (interfaces.contains(HAN_FUN_INTERFACE_SIMPLE_BUTTON)) {
return DEVICE_HAN_FUN_SWITCH;
}
}
return device.getProductName().replaceAll(INVALID_PATTERN, "_");
}
/**
*
* @param device Discovered device model
* @return Thing name without illegal characters.
*/
public String getThingName(AVMFritzBaseModel device) {
return device.getIdentifier().replaceAll(INVALID_PATTERN, "_");
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
String channelId = channelUID.getIdWithoutGroup();
logger.debug("Handle command '{}' for channel {}", command, channelId);
if (command == RefreshType.REFRESH) {
handleRefreshCommand();
return;
}
FritzAhaWebInterface fritzBox = getWebInterface();
if (fritzBox == null) {
logger.debug("Cannot handle command '{}' because connection is missing", command);
return;
}
if (CHANNEL_APPLY_TEMPLATE.equals(channelId)) {
if (command instanceof StringType) {
fritzBox.applyTemplate(command.toString());
}
} else {
logger.debug("Received unknown channel {}", channelId);
}
}
/**
* Provides the web interface object.
*
* @return The web interface object
*/
public @Nullable FritzAhaWebInterface getWebInterface() {
return connection;
}
/**
* Handles a refresh command.
*/
public void handleRefreshCommand() {
scheduler.submit(this::poll);
}
}

View File

@@ -0,0 +1,477 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.handler;
import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.*;
import static org.openhab.binding.avmfritz.internal.dto.HeatingModel.*;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Map;
import javax.measure.quantity.Temperature;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.avmfritz.internal.config.AVMFritzDeviceConfiguration;
import org.openhab.binding.avmfritz.internal.dto.AVMFritzBaseModel;
import org.openhab.binding.avmfritz.internal.dto.AlertModel;
import org.openhab.binding.avmfritz.internal.dto.BatteryModel;
import org.openhab.binding.avmfritz.internal.dto.DeviceModel;
import org.openhab.binding.avmfritz.internal.dto.HeatingModel;
import org.openhab.binding.avmfritz.internal.dto.HeatingModel.NextChangeModel;
import org.openhab.binding.avmfritz.internal.dto.PowerMeterModel;
import org.openhab.binding.avmfritz.internal.dto.SwitchModel;
import org.openhab.binding.avmfritz.internal.dto.TemperatureModel;
import org.openhab.binding.avmfritz.internal.hardware.FritzAhaStatusListener;
import org.openhab.binding.avmfritz.internal.hardware.FritzAhaWebInterface;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
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.SmartHomeUnits;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.DefaultSystemChannelTypeProvider;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.BridgeHandler;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract handler for a FRITZ! thing. Handles commands, which are sent to one of the channels.
*
* @author Robert Bausdorf - Initial contribution
* @author Christoph Weitkamp - Added support for AVM FRITZ!DECT 300 and Comet DECT
* @author Christoph Weitkamp - Added support for groups
*/
@NonNullByDefault
public abstract class AVMFritzBaseThingHandler extends BaseThingHandler implements FritzAhaStatusListener {
private final Logger logger = LoggerFactory.getLogger(AVMFritzBaseThingHandler.class);
/**
* keeps track of the current state for handling of increase/decrease
*/
private @Nullable AVMFritzBaseModel state;
private @NonNullByDefault({}) AVMFritzDeviceConfiguration config;
/**
* Constructor
*
* @param thing Thing object representing a FRITZ! device
*/
public AVMFritzBaseThingHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
config = getConfigAs(AVMFritzDeviceConfiguration.class);
String newIdentifier = config.ain;
if (newIdentifier == null || newIdentifier.trim().isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"The 'ain' parameter must be configured.");
} else {
updateStatus(ThingStatus.UNKNOWN);
}
}
@Override
public void onDeviceAdded(AVMFritzBaseModel device) {
// nothing to do
}
@Override
public void onDeviceUpdated(ThingUID thingUID, AVMFritzBaseModel device) {
if (thing.getUID().equals(thingUID)) {
logger.debug("Update thing '{}' with device model: {}", thingUID, device);
if (device.getPresent() == 1) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Device not present");
}
state = device;
updateProperties(device, editProperties());
if (device.isPowermeter()) {
updatePowermeter(device.getPowermeter());
}
if (device.isSwitchableOutlet()) {
updateSwitchableOutlet(device.getSwitch());
}
if (device.isHeatingThermostat()) {
updateHeatingThermostat(device.getHkr());
}
if (device instanceof DeviceModel) {
DeviceModel deviceModel = (DeviceModel) device;
if (deviceModel.isTempSensor()) {
updateTemperatureSensor(deviceModel.getTemperature());
}
if (deviceModel.isHANFUNAlarmSensor()) {
updateHANFUNAlarmSensor(deviceModel.getAlert());
}
}
}
}
private void updateHANFUNAlarmSensor(@Nullable AlertModel alertModel) {
if (alertModel != null) {
updateThingChannelState(CHANNEL_CONTACT_STATE,
AlertModel.ON.equals(alertModel.getState()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
}
}
private void updateTemperatureSensor(@Nullable TemperatureModel temperatureModel) {
if (temperatureModel != null) {
updateThingChannelState(CHANNEL_TEMPERATURE,
new QuantityType<>(temperatureModel.getCelsius(), SIUnits.CELSIUS));
updateThingChannelConfiguration(CHANNEL_TEMPERATURE, CONFIG_CHANNEL_TEMP_OFFSET,
temperatureModel.getOffset());
}
}
private void updateHeatingThermostat(@Nullable HeatingModel heatingModel) {
if (heatingModel != null) {
updateThingChannelState(CHANNEL_MODE, new StringType(heatingModel.getMode()));
updateThingChannelState(CHANNEL_LOCKED,
BigDecimal.ZERO.equals(heatingModel.getLock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
updateThingChannelState(CHANNEL_DEVICE_LOCKED,
BigDecimal.ZERO.equals(heatingModel.getDevicelock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
updateThingChannelState(CHANNEL_ACTUALTEMP,
new QuantityType<>(toCelsius(heatingModel.getTist()), SIUnits.CELSIUS));
updateThingChannelState(CHANNEL_SETTEMP,
new QuantityType<>(toCelsius(heatingModel.getTsoll()), SIUnits.CELSIUS));
updateThingChannelState(CHANNEL_ECOTEMP,
new QuantityType<>(toCelsius(heatingModel.getAbsenk()), SIUnits.CELSIUS));
updateThingChannelState(CHANNEL_COMFORTTEMP,
new QuantityType<>(toCelsius(heatingModel.getKomfort()), SIUnits.CELSIUS));
updateThingChannelState(CHANNEL_RADIATOR_MODE, new StringType(heatingModel.getRadiatorMode()));
NextChangeModel nextChange = heatingModel.getNextchange();
if (nextChange != null) {
int endPeriod = nextChange.getEndperiod();
updateThingChannelState(CHANNEL_NEXT_CHANGE, endPeriod == 0 ? UnDefType.UNDEF
: new DateTimeType(
ZonedDateTime.ofInstant(Instant.ofEpochSecond(endPeriod), ZoneId.systemDefault())));
BigDecimal nextTemperature = nextChange.getTchange();
updateThingChannelState(CHANNEL_NEXTTEMP, TEMP_FRITZ_UNDEFINED.equals(nextTemperature) ? UnDefType.UNDEF
: new QuantityType<>(toCelsius(nextTemperature), SIUnits.CELSIUS));
}
updateBattery(heatingModel);
}
}
protected void updateBattery(BatteryModel batteryModel) {
BigDecimal batteryLevel = batteryModel.getBattery();
updateThingChannelState(CHANNEL_BATTERY,
batteryLevel == null ? UnDefType.UNDEF : new DecimalType(batteryLevel));
BigDecimal lowBattery = batteryModel.getBatterylow();
if (lowBattery == null) {
updateThingChannelState(CHANNEL_BATTERY_LOW, UnDefType.UNDEF);
} else {
updateThingChannelState(CHANNEL_BATTERY_LOW,
BatteryModel.BATTERY_ON.equals(lowBattery) ? OnOffType.ON : OnOffType.OFF);
}
}
private void updateSwitchableOutlet(@Nullable SwitchModel switchModel) {
if (switchModel != null) {
updateThingChannelState(CHANNEL_MODE, new StringType(switchModel.getMode()));
updateThingChannelState(CHANNEL_LOCKED,
BigDecimal.ZERO.equals(switchModel.getLock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
updateThingChannelState(CHANNEL_DEVICE_LOCKED,
BigDecimal.ZERO.equals(switchModel.getDevicelock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
BigDecimal state = switchModel.getState();
if (state == null) {
updateThingChannelState(CHANNEL_OUTLET, UnDefType.UNDEF);
} else {
updateThingChannelState(CHANNEL_OUTLET, SwitchModel.ON.equals(state) ? OnOffType.ON : OnOffType.OFF);
}
}
}
private void updatePowermeter(@Nullable PowerMeterModel powerMeterModel) {
if (powerMeterModel != null) {
updateThingChannelState(CHANNEL_ENERGY,
new QuantityType<>(powerMeterModel.getEnergy(), SmartHomeUnits.WATT_HOUR));
updateThingChannelState(CHANNEL_POWER, new QuantityType<>(powerMeterModel.getPower(), SmartHomeUnits.WATT));
updateThingChannelState(CHANNEL_VOLTAGE,
new QuantityType<>(powerMeterModel.getVoltage(), SmartHomeUnits.VOLT));
}
}
/**
* Updates thing properties.
*
* @param device the {@link AVMFritzBaseModel}
* @param editProperties map of existing properties
*/
protected void updateProperties(AVMFritzBaseModel device, Map<String, String> editProperties) {
editProperties.put(Thing.PROPERTY_FIRMWARE_VERSION, device.getFirmwareVersion());
updateProperties(editProperties);
}
/**
* Updates thing channels and creates dynamic channels if missing.
*
* @param channelId ID of the channel to be updated.
* @param state State to be set.
*/
protected void updateThingChannelState(String channelId, State state) {
Channel channel = thing.getChannel(channelId);
if (channel != null) {
updateState(channel.getUID(), state);
} else {
logger.debug("Channel '{}' in thing '{}' does not exist, recreating thing.", channelId, thing.getUID());
createChannel(channelId);
}
}
/**
* Creates new channels for the thing.
*
* @param channelId ID of the channel to be created.
*/
private void createChannel(String channelId) {
ThingHandlerCallback callback = getCallback();
if (callback != null) {
ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
ChannelTypeUID channelTypeUID = CHANNEL_BATTERY.equals(channelId)
? DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_BATTERY_LEVEL.getUID()
: new ChannelTypeUID(BINDING_ID, channelId);
Channel channel = callback.createChannelBuilder(channelUID, channelTypeUID).build();
updateThing(editThing().withoutChannel(channelUID).withChannel(channel).build());
}
}
/**
* Updates thing channel configurations.
*
* @param channelId ID of the channel which configuration to be updated.
* @param configId ID of the configuration to be updated.
* @param value Value to be set.
*/
private void updateThingChannelConfiguration(String channelId, String configId, Object value) {
Channel channel = thing.getChannel(channelId);
if (channel != null) {
Configuration editConfig = channel.getConfiguration();
editConfig.put(configId, value);
}
}
@Override
public void onDeviceGone(ThingUID thingUID) {
if (thing.getUID().equals(thingUID)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, "Device not present in response");
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
String channelId = channelUID.getIdWithoutGroup();
logger.debug("Handle command '{}' for channel {}", command, channelId);
if (command == RefreshType.REFRESH) {
handleRefreshCommand();
return;
}
FritzAhaWebInterface fritzBox = getWebInterface();
if (fritzBox == null) {
logger.debug("Cannot handle command '{}' because connection is missing", command);
return;
}
String ain = getIdentifier();
if (ain == null) {
logger.debug("Cannot handle command '{}' because AIN is missing", command);
return;
}
switch (channelId) {
case CHANNEL_MODE:
case CHANNEL_LOCKED:
case CHANNEL_DEVICE_LOCKED:
case CHANNEL_TEMPERATURE:
case CHANNEL_ENERGY:
case CHANNEL_POWER:
case CHANNEL_VOLTAGE:
case CHANNEL_ACTUALTEMP:
case CHANNEL_ECOTEMP:
case CHANNEL_COMFORTTEMP:
case CHANNEL_NEXT_CHANGE:
case CHANNEL_NEXTTEMP:
case CHANNEL_BATTERY:
case CHANNEL_BATTERY_LOW:
case CHANNEL_CONTACT_STATE:
case CHANNEL_LAST_CHANGE:
logger.debug("Channel {} is a read-only channel and cannot handle command '{}'", channelId, command);
break;
case CHANNEL_OUTLET:
if (command instanceof OnOffType) {
fritzBox.setSwitch(ain, OnOffType.ON.equals(command));
if (state != null) {
state.getSwitch().setState(OnOffType.ON.equals(command) ? SwitchModel.ON : SwitchModel.OFF);
}
}
break;
case CHANNEL_SETTEMP:
BigDecimal temperature = null;
if (command instanceof DecimalType) {
temperature = normalizeCelsius(((DecimalType) command).toBigDecimal());
} else if (command instanceof QuantityType) {
temperature = normalizeCelsius(
((QuantityType<Temperature>) command).toUnit(SIUnits.CELSIUS).toBigDecimal());
} else if (command instanceof IncreaseDecreaseType) {
temperature = state.getHkr().getTsoll();
if (IncreaseDecreaseType.INCREASE.equals(command)) {
temperature.add(BigDecimal.ONE);
} else {
temperature.subtract(BigDecimal.ONE);
}
} else if (command instanceof OnOffType) {
temperature = OnOffType.ON.equals(command) ? TEMP_FRITZ_ON : TEMP_FRITZ_OFF;
}
if (temperature != null) {
fritzBox.setSetTemp(ain, fromCelsius(temperature));
HeatingModel heatingModel = state.getHkr();
heatingModel.setTsoll(temperature);
updateState(CHANNEL_RADIATOR_MODE, new StringType(heatingModel.getRadiatorMode()));
}
break;
case CHANNEL_RADIATOR_MODE:
BigDecimal targetTemperature = null;
if (command instanceof StringType) {
switch (command.toString()) {
case MODE_ON:
targetTemperature = TEMP_FRITZ_ON;
break;
case MODE_OFF:
targetTemperature = TEMP_FRITZ_OFF;
break;
case MODE_COMFORT:
targetTemperature = state.getHkr().getKomfort();
break;
case MODE_ECO:
targetTemperature = state.getHkr().getAbsenk();
break;
case MODE_BOOST:
targetTemperature = TEMP_FRITZ_MAX;
break;
case MODE_UNKNOWN:
case MODE_WINDOW_OPEN:
logger.debug("Command '{}' is a read-only command for channel {}.", command, channelId);
break;
}
if (targetTemperature != null) {
fritzBox.setSetTemp(ain, targetTemperature);
state.getHkr().setTsoll(targetTemperature);
updateState(CHANNEL_SETTEMP, new QuantityType<>(toCelsius(targetTemperature), SIUnits.CELSIUS));
}
}
break;
default:
logger.debug("Received unknown channel {}", channelId);
break;
}
}
/**
* Handles a command for a given action.
*
* @param action
* @param duration
*/
protected void handleAction(String action, long duration) {
FritzAhaWebInterface fritzBox = getWebInterface();
if (fritzBox == null) {
logger.debug("Cannot handle action '{}' because connection is missing", action);
return;
}
String ain = getIdentifier();
if (ain == null) {
logger.debug("Cannot handle action '{}' because AIN is missing", action);
return;
}
if (duration < 0 || 86400 < duration) {
throw new IllegalArgumentException("Duration must not be less than zero or greater than 86400");
}
switch (action) {
case MODE_BOOST:
fritzBox.setBoostMode(ain,
duration > 0 ? ZonedDateTime.now().plusSeconds(duration).toEpochSecond() : 0);
break;
case MODE_WINDOW_OPEN:
fritzBox.setWindowOpenMode(ain,
duration > 0 ? ZonedDateTime.now().plusSeconds(duration).toEpochSecond() : 0);
break;
default:
logger.debug("Received unknown action '{}'", action);
break;
}
}
/**
* Provides the web interface object.
*
* @return The web interface object
*/
private @Nullable FritzAhaWebInterface getWebInterface() {
Bridge bridge = getBridge();
if (bridge != null) {
BridgeHandler handler = bridge.getHandler();
if (handler instanceof AVMFritzBaseBridgeHandler) {
return ((AVMFritzBaseBridgeHandler) handler).getWebInterface();
}
}
return null;
}
/**
* Handles a refresh command.
*/
private void handleRefreshCommand() {
Bridge bridge = getBridge();
if (bridge != null) {
BridgeHandler handler = bridge.getHandler();
if (handler instanceof AVMFritzBaseBridgeHandler) {
((AVMFritzBaseBridgeHandler) handler).handleRefreshCommand();
}
}
}
/**
* Returns the AIN.
*
* @return the AIN
*/
public @Nullable String getIdentifier() {
return config.ain;
}
}

View File

@@ -0,0 +1,129 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.handler;
import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.*;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
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.CommonTriggerEvents;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handler for a FRITZ! buttons. Handles commands, which are sent to one of the channels.
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class AVMFritzButtonHandler extends DeviceHandler {
private final Logger logger = LoggerFactory.getLogger(AVMFritzButtonHandler.class);
/**
* keeps track of the last timestamp for handling trigger events
*/
private Instant lastTimestamp;
/**
* Constructor
*
* @param thing Thing object representing a FRITZ! button
*/
public AVMFritzButtonHandler(Thing thing) {
super(thing);
lastTimestamp = Instant.now();
}
@Override
public void onDeviceUpdated(ThingUID thingUID, AVMFritzBaseModel device) {
if (thing.getUID().equals(thingUID)) {
super.onDeviceUpdated(thingUID, device);
if (device instanceof DeviceModel) {
DeviceModel deviceModel = (DeviceModel) device;
if (deviceModel.isHANFUNButton()) {
updateHANFUNButton(deviceModel.getButtons());
}
if (deviceModel.isButton()) {
updateShortLongPressButton(deviceModel.getButtons());
updateBattery(deviceModel);
}
}
}
}
private void updateShortLongPressButton(List<ButtonModel> buttons) {
ButtonModel shortPressButton = buttons.size() > 0 ? buttons.get(0) : null;
ButtonModel longPressButton = buttons.size() > 1 ? buttons.get(1) : null;
ButtonModel lastPressedButton = shortPressButton != null && (longPressButton == null
|| shortPressButton.getLastpressedtimestamp() > longPressButton.getLastpressedtimestamp())
? shortPressButton
: longPressButton;
if (lastPressedButton != null) {
updateButton(lastPressedButton,
lastPressedButton.equals(shortPressButton) ? CommonTriggerEvents.SHORT_PRESSED
: CommonTriggerEvents.LONG_PRESSED);
}
}
private void updateHANFUNButton(List<ButtonModel> buttons) {
if (!buttons.isEmpty()) {
updateButton(buttons.get(0), CommonTriggerEvents.PRESSED);
}
}
private void updateButton(ButtonModel buttonModel, String event) {
int lastPressedTimestamp = buttonModel.getLastpressedtimestamp();
if (lastPressedTimestamp == 0) {
updateThingChannelState(CHANNEL_LAST_CHANGE, UnDefType.UNDEF);
} else {
ZonedDateTime timestamp = ZonedDateTime.ofInstant(Instant.ofEpochSecond(lastPressedTimestamp),
ZoneId.systemDefault());
Instant then = timestamp.toInstant();
// Avoid dispatching events if "lastpressedtimestamp" is older than now "lastTimestamp" (e.g. during
// restart)
if (then.isAfter(lastTimestamp)) {
lastTimestamp = then;
triggerThingChannel(CHANNEL_PRESS, event);
}
updateThingChannelState(CHANNEL_LAST_CHANGE, new DateTimeType(timestamp));
}
}
/**
* Triggers thing channels.
*
* @param channelId ID of the channel to be triggered.
* @param event Event to emit
*/
private void triggerThingChannel(String channelId, String event) {
Channel channel = thing.getChannel(channelId);
if (channel != null) {
triggerChannel(channel.getUID(), event);
} else {
logger.debug("Channel '{}' in thing '{}' does not exist.", channelId, thing.getUID());
}
}
}

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.handler;
import java.util.Collection;
import java.util.Collections;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.avmfritz.actions.AVMFritzHeatingActions;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
/**
* The {@link AVMFritzHeatingActionsHandler} defines interface handlers to handle heating thing actions.
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public interface AVMFritzHeatingActionsHandler extends ThingHandler {
@Override
default Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(AVMFritzHeatingActions.class);
}
/**
* Activates the "Boost" mode of the heating thermostat or heating group.
*
* @param duration Duration in seconds, min. 1, max. 86400, 0 for deactivation.
*/
void setBoostMode(long duration);
/**
* Activates the "Window Open" mode of the heating thermostat or heating group.
*
* @param duration Duration in seconds, min. 1, max. 86400, 0 for deactivation.
*/
void setWindowOpenMode(long duration);
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.handler;
import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.Thing;
/**
* Handler for a FRITZ! heating device. Handles commands, which are sent to one of the channels.
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class AVMFritzHeatingDeviceHandler extends DeviceHandler implements AVMFritzHeatingActionsHandler {
public AVMFritzHeatingDeviceHandler(Thing thing) {
super(thing);
}
@Override
public void setBoostMode(long duration) {
handleAction(MODE_BOOST, duration);
}
@Override
public void setWindowOpenMode(long duration) {
handleAction(MODE_WINDOW_OPEN, duration);
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.handler;
import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.Thing;
/**
* Handler for a FRITZ! heating group. Handles commands, which are sent to one of the channels.
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class AVMFritzHeatingGroupHandler extends GroupHandler implements AVMFritzHeatingActionsHandler {
public AVMFritzHeatingGroupHandler(Thing thing) {
super(thing);
}
@Override
public void setBoostMode(long duration) {
handleAction(MODE_BOOST, duration);
}
@Override
public void setWindowOpenMode(long duration) {
handleAction(MODE_WINDOW_OPEN, duration);
}
}

View File

@@ -0,0 +1,104 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.handler;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants;
import org.openhab.binding.avmfritz.internal.AVMFritzDynamicCommandDescriptionProvider;
import org.openhab.binding.avmfritz.internal.callmonitor.CallMonitor;
import org.openhab.binding.avmfritz.internal.config.AVMFritzBoxConfiguration;
import org.openhab.binding.avmfritz.internal.hardware.FritzAhaWebInterface;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.State;
/**
* Handler for a FRITZ!Box device. Handles polling of values from AHA devices.
*
* @author Robert Bausdorf - Initial contribution
* @author Christoph Weitkamp - Added support for groups
* @author Kai Kreuzer - Added call monitor support
*/
@NonNullByDefault
public class BoxHandler extends AVMFritzBaseBridgeHandler {
protected static final Set<String> CALL_CHANNELS = new HashSet<>();
static {
// TODO: We are still on Java 8 and cannot use Set.of
CALL_CHANNELS.add(AVMFritzBindingConstants.CHANNEL_CALL_ACTIVE);
CALL_CHANNELS.add(AVMFritzBindingConstants.CHANNEL_CALL_INCOMING);
CALL_CHANNELS.add(AVMFritzBindingConstants.CHANNEL_CALL_OUTGOING);
CALL_CHANNELS.add(AVMFritzBindingConstants.CHANNEL_CALL_STATE);
}
private @Nullable CallMonitor callMonitor;
/**
* Constructor
*
* @param bridge Bridge object representing a FRITZ!Box
*/
public BoxHandler(Bridge bridge, HttpClient httpClient,
AVMFritzDynamicCommandDescriptionProvider commandDescriptionProvider) {
super(bridge, httpClient, commandDescriptionProvider);
}
@Override
protected void manageConnections() {
AVMFritzBoxConfiguration config = getConfigAs(AVMFritzBoxConfiguration.class);
if (this.callMonitor == null && callChannelsLinked()) {
this.callMonitor = new CallMonitor(config.ipAddress, this, scheduler);
} else if (this.callMonitor != null && !callChannelsLinked()) {
CallMonitor cm = this.callMonitor;
cm.dispose();
this.callMonitor = null;
}
if (this.connection == null) {
if (config.password != null) {
this.connection = new FritzAhaWebInterface(config, this, httpClient);
stopPolling();
startPolling();
} else {
if (!callChannelsLinked()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"The 'password' parameter must be configured to use the AHA features.");
}
}
}
}
private boolean callChannelsLinked() {
return getThing().getChannels().stream()
.filter(c -> isLinked(c.getUID()) && CALL_CHANNELS.contains(c.getUID().getId())).count() > 0;
}
@Override
public void dispose() {
if (callMonitor != null) {
callMonitor.dispose();
callMonitor = null;
}
super.dispose();
}
@Override
public void updateState(String channelID, State state) {
super.updateState(channelID, state);
}
}

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.Thing;
/**
* Handler for a FRITZ! device. Handles commands, which are sent to one of the channels.
*
* @author Robert Bausdorf - Initial contribution
* @author Christoph Weitkamp - Added support for AVM FRITZ!DECT 300 and Comet DECT
* @author Christoph Weitkamp - Added support for groups
*/
@NonNullByDefault
public class DeviceHandler extends AVMFritzBaseThingHandler {
public DeviceHandler(Thing thing) {
super(thing);
}
}

View File

@@ -0,0 +1,47 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.handler;
import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.*;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.avmfritz.internal.dto.AVMFritzBaseModel;
import org.openhab.binding.avmfritz.internal.dto.GroupModel;
import org.openhab.core.thing.Thing;
/**
* Handler for a FRITZ! group. Handles commands, which are sent to one of the channels.
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class GroupHandler extends AVMFritzBaseThingHandler {
public GroupHandler(Thing thing) {
super(thing);
}
@Override
protected void updateProperties(AVMFritzBaseModel device, Map<String, String> editProperties) {
if (device instanceof GroupModel) {
GroupModel groupModel = (GroupModel) device;
if (groupModel.getGroupinfo() != null) {
editProperties.put(PROPERTY_MASTER, groupModel.getGroupinfo().getMasterdeviceid());
editProperties.put(PROPERTY_MEMBERS, groupModel.getGroupinfo().getMembers());
}
}
super.updateProperties(device, editProperties);
}
}

View File

@@ -0,0 +1,317 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.handler;
import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.*;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants;
import org.openhab.binding.avmfritz.internal.AVMFritzDynamicCommandDescriptionProvider;
import org.openhab.binding.avmfritz.internal.config.AVMFritzBoxConfiguration;
import org.openhab.binding.avmfritz.internal.config.AVMFritzDeviceConfiguration;
import org.openhab.binding.avmfritz.internal.dto.AVMFritzBaseModel;
import org.openhab.binding.avmfritz.internal.dto.PowerMeterModel;
import org.openhab.binding.avmfritz.internal.dto.SwitchModel;
import org.openhab.binding.avmfritz.internal.hardware.FritzAhaStatusListener;
import org.openhab.binding.avmfritz.internal.hardware.FritzAhaWebInterface;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.DefaultSystemChannelTypeProvider;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handler for a FRITZ!Powerline 546E device. Handles polling of values from AHA devices and commands, which are sent to
* one of the channels.
*
* @author Robert Bausdorf - Initial contribution
* @author Christoph Weitkamp - Added support for groups
*/
@NonNullByDefault
public class Powerline546EHandler extends AVMFritzBaseBridgeHandler implements FritzAhaStatusListener {
private final Logger logger = LoggerFactory.getLogger(Powerline546EHandler.class);
/**
* keeps track of the current state for handling of increase/decrease
*/
private @Nullable AVMFritzBaseModel state;
private @Nullable AVMFritzDeviceConfiguration config;
/**
* Constructor
*
* @param bridge Bridge object representing a FRITZ!Powerline 546E
*/
public Powerline546EHandler(Bridge bridge, HttpClient httpClient,
AVMFritzDynamicCommandDescriptionProvider commandDescriptionProvider) {
super(bridge, httpClient, commandDescriptionProvider);
}
@Override
public void initialize() {
config = getConfigAs(AVMFritzDeviceConfiguration.class);
registerStatusListener(this);
super.initialize();
}
@Override
public void dispose() {
unregisterStatusListener(this);
super.dispose();
}
@Override
public void onDeviceListAdded(List<AVMFritzBaseModel> devicelist) {
final String identifier = getIdentifier();
final Predicate<AVMFritzBaseModel> predicate = identifier == null ? it -> thing.getUID().equals(getThingUID(it))
: it -> identifier.equals(it.getIdentifier());
final Optional<AVMFritzBaseModel> optionalDevice = devicelist.stream().filter(predicate).findFirst();
if (optionalDevice.isPresent()) {
final AVMFritzBaseModel device = optionalDevice.get();
devicelist.remove(device);
listeners.stream().forEach(listener -> listener.onDeviceUpdated(thing.getUID(), device));
} else {
listeners.stream().forEach(listener -> listener.onDeviceGone(thing.getUID()));
}
super.onDeviceListAdded(devicelist);
}
@Override
public void onDeviceAdded(AVMFritzBaseModel device) {
// nothing to do
}
@Override
public void onDeviceUpdated(ThingUID thingUID, AVMFritzBaseModel device) {
if (thing.getUID().equals(thingUID)) {
// save AIN to config for FRITZ!Powerline 546E stand-alone
if (config == null) {
updateConfiguration(device);
}
logger.debug("Update self '{}' with device model: {}", thingUID, device);
if (device.getPresent() == 1) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Device not present");
}
state = device;
updateProperties(device);
if (device.isPowermeter()) {
updatePowermeter(device.getPowermeter());
}
if (device.isSwitchableOutlet()) {
updateSwitchableOutlet(device.getSwitch());
}
}
}
private void updateSwitchableOutlet(@Nullable SwitchModel switchModel) {
if (switchModel != null) {
updateThingChannelState(CHANNEL_MODE, new StringType(switchModel.getMode()));
updateThingChannelState(CHANNEL_LOCKED,
BigDecimal.ZERO.equals(switchModel.getLock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
updateThingChannelState(CHANNEL_DEVICE_LOCKED,
BigDecimal.ZERO.equals(switchModel.getDevicelock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
BigDecimal state = switchModel.getState();
if (state == null) {
updateThingChannelState(CHANNEL_OUTLET, UnDefType.UNDEF);
} else {
updateThingChannelState(CHANNEL_OUTLET, SwitchModel.ON.equals(state) ? OnOffType.ON : OnOffType.OFF);
}
}
}
private void updatePowermeter(@Nullable PowerMeterModel powerMeterModel) {
if (powerMeterModel != null) {
updateThingChannelState(CHANNEL_ENERGY,
new QuantityType<>(powerMeterModel.getEnergy(), SmartHomeUnits.WATT_HOUR));
updateThingChannelState(CHANNEL_POWER, new QuantityType<>(powerMeterModel.getPower(), SmartHomeUnits.WATT));
updateThingChannelState(CHANNEL_VOLTAGE,
new QuantityType<>(powerMeterModel.getVoltage(), SmartHomeUnits.VOLT));
}
}
/**
* Updates thing properties.
*
* @param device the {@link AVMFritzBaseModel}
*/
private void updateProperties(AVMFritzBaseModel device) {
Map<String, String> editProperties = editProperties();
editProperties.put(Thing.PROPERTY_FIRMWARE_VERSION, device.getFirmwareVersion());
updateProperties(editProperties);
}
/**
* Updates thing configuration.
*
* @param device the {@link AVMFritzBaseModel}
*/
private void updateConfiguration(AVMFritzBaseModel device) {
Configuration editConfig = editConfiguration();
editConfig.put(CONFIG_AIN, device.getIdentifier());
updateConfiguration(editConfig);
}
/**
* Updates thing channels and creates dynamic channels if missing.
*
* @param channelId ID of the channel to be updated.
* @param state State to be set.
*/
private void updateThingChannelState(String channelId, State state) {
Channel channel = thing.getChannel(channelId);
if (channel != null) {
updateState(channel.getUID(), state);
} else {
logger.debug("Channel '{}' in thing '{}' does not exist, recreating thing.", channelId, thing.getUID());
createChannel(channelId);
}
}
/**
* Creates new channels for the thing.
*
* @param channelId ID of the channel to be created.
*/
private void createChannel(String channelId) {
ThingHandlerCallback callback = getCallback();
if (callback != null) {
ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
ChannelTypeUID channelTypeUID = CHANNEL_BATTERY.equals(channelId)
? DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_BATTERY_LEVEL.getUID()
: new ChannelTypeUID(BINDING_ID, channelId);
Channel channel = callback.createChannelBuilder(channelUID, channelTypeUID).build();
updateThing(editThing().withoutChannel(channelUID).withChannel(channel).build());
}
}
@Override
public void onDeviceGone(ThingUID thingUID) {
if (thing.getUID().equals(thingUID)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, "Device not present in response");
}
}
/**
* Builds a {@link ThingUID} from a device model. The UID is build from the
* {@link AVMFritzBindingConstants#BINDING_ID} and value of
* {@link AVMFritzBaseModel#getProductName()} in which all characters NOT matching
* the regex [^a-zA-Z0-9_] are replaced by "_".
*
* @param device Discovered device model
* @return ThingUID without illegal characters.
*/
@Override
public @Nullable ThingUID getThingUID(AVMFritzBaseModel device) {
ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, getThingTypeId(device).concat("_Solo"));
String ipAddress = getConfigAs(AVMFritzBoxConfiguration.class).ipAddress;
if (PL546E_STANDALONE_THING_TYPE.equals(thingTypeUID)) {
String thingName = "fritz.powerline".equals(ipAddress) ? ipAddress
: ipAddress.replaceAll(INVALID_PATTERN, "_");
return new ThingUID(thingTypeUID, thingName);
} else {
return super.getThingUID(device);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
String channelId = channelUID.getIdWithoutGroup();
logger.debug("Handle command '{}' for channel {}", command, channelId);
if (command == RefreshType.REFRESH) {
handleRefreshCommand();
return;
}
FritzAhaWebInterface fritzBox = getWebInterface();
if (fritzBox == null) {
logger.debug("Cannot handle command '{}' because connection is missing", command);
return;
}
String ain = getIdentifier();
if (ain == null) {
logger.debug("Cannot handle command '{}' because AIN is missing", command);
return;
}
switch (channelId) {
case CHANNEL_MODE:
case CHANNEL_LOCKED:
case CHANNEL_DEVICE_LOCKED:
case CHANNEL_ENERGY:
case CHANNEL_POWER:
case CHANNEL_VOLTAGE:
logger.debug("Channel {} is a read-only channel and cannot handle command '{}'", channelId, command);
break;
case CHANNEL_APPLY_TEMPLATE:
if (command instanceof StringType) {
fritzBox.applyTemplate(command.toString());
}
break;
case CHANNEL_OUTLET:
fritzBox.setSwitch(ain, OnOffType.ON.equals(command));
if (command instanceof OnOffType) {
if (state != null) {
state.getSwitch().setState(OnOffType.ON.equals(command) ? SwitchModel.ON : SwitchModel.OFF);
}
}
break;
default:
super.handleCommand(channelUID, command);
break;
}
}
/**
* Returns the AIN.
*
* @return the AIN
*/
public @Nullable String getIdentifier() {
AVMFritzDeviceConfiguration localConfig = config;
return localConfig != null ? localConfig.ain : null;
}
}

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.hardware;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Response.CompleteListener;
import org.eclipse.jetty.client.api.Response.ContentListener;
import org.eclipse.jetty.client.api.Response.FailureListener;
import org.eclipse.jetty.client.api.Response.SuccessListener;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.openhab.binding.avmfritz.internal.hardware.callbacks.FritzAhaCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation of Jetty ContextExchange to handle callbacks
*
* @author Robert Bausdorf - Initial contribution
*/
@NonNullByDefault
public class FritzAhaContentExchange extends BufferingResponseListener
implements SuccessListener, FailureListener, ContentListener, CompleteListener {
private final Logger logger = LoggerFactory.getLogger(FritzAhaContentExchange.class);
/**
* Callback to execute on complete response
*/
private final FritzAhaCallback callback;
/**
* Constructor
*
* @param callback Callback which execute method has to be called.
*/
public FritzAhaContentExchange(FritzAhaCallback callback) {
this.callback = callback;
}
@Override
public void onSuccess(@NonNullByDefault({}) Response response) {
logger.debug("{} response: {}", response.getRequest().getScheme().toUpperCase(), response.getStatus());
}
@Override
public void onFailure(@NonNullByDefault({}) Response response, @NonNullByDefault({}) Throwable failure) {
logger.debug("response failed: {}", failure.getLocalizedMessage(), failure);
}
@Override
public void onComplete(@NonNullByDefault({}) Result result) {
String content = getContentAsString();
logger.debug("response complete: {}", content);
callback.execute(result.getResponse().getStatus(), content);
}
}

View File

@@ -0,0 +1,48 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.hardware;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.avmfritz.internal.dto.AVMFritzBaseModel;
import org.openhab.core.thing.ThingUID;
/**
* The {@link FritzAhaStatusListener} is notified when a new device has been added, removed or its status has changed.
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public interface FritzAhaStatusListener {
/**
* This method is called whenever a device is added.
*
* @param device the {@link AVMFritzBaseModel}
*/
void onDeviceAdded(AVMFritzBaseModel device);
/**
* This method is called whenever a device is updated.
*
* @param thingUID the {@link ThingUID}
* @param device the {@link AVMFritzBaseModel}
*/
void onDeviceUpdated(ThingUID thingUID, AVMFritzBaseModel device);
/**
* This method is called whenever a device is gone.
*
* @param thingUID the {@link ThingUID}
*/
void onDeviceGone(ThingUID thingUID);
}

View File

@@ -0,0 +1,318 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.hardware;
import static org.eclipse.jetty.http.HttpMethod.*;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.openhab.binding.avmfritz.internal.config.AVMFritzBoxConfiguration;
import org.openhab.binding.avmfritz.internal.handler.AVMFritzBaseBridgeHandler;
import org.openhab.binding.avmfritz.internal.hardware.callbacks.FritzAhaApplyTemplateCallback;
import org.openhab.binding.avmfritz.internal.hardware.callbacks.FritzAhaCallback;
import org.openhab.binding.avmfritz.internal.hardware.callbacks.FritzAhaSetHeatingModeCallback;
import org.openhab.binding.avmfritz.internal.hardware.callbacks.FritzAhaSetHeatingTemperatureCallback;
import org.openhab.binding.avmfritz.internal.hardware.callbacks.FritzAhaSetSwitchCallback;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class handles requests to a FRITZ!OS web interface for interfacing with AVM home automation devices. It manages
* authentication and wraps commands.
*
* @author Robert Bausdorf, Christian Brauers - Initial contribution
* @author Christoph Weitkamp - Added support for AVM FRITZ!DECT 300 and Comet
* DECT
* @author Christoph Weitkamp - Added support for groups
*/
@NonNullByDefault
public class FritzAhaWebInterface {
private static final String WEBSERVICE_PATH = "login_sid.lua";
/**
* RegEx Pattern to grab the session ID from a login XML response
*/
private static final Pattern SID_PATTERN = Pattern.compile("<SID>([a-fA-F0-9]*)</SID>");
/**
* RegEx Pattern to grab the challenge from a login XML response
*/
private static final Pattern CHALLENGE_PATTERN = Pattern.compile("<Challenge>(\\w*)</Challenge>");
/**
* RegEx Pattern to grab the access privilege for home automation functions from a login XML response
*/
private static final Pattern ACCESS_PATTERN = Pattern.compile("<Name>HomeAuto</Name>\\s*?<Access>([0-9])</Access>");
private final Logger logger = LoggerFactory.getLogger(FritzAhaWebInterface.class);
/**
* Configuration of the bridge from {@link AVMFritzBaseBridgeHandler}
*/
private final AVMFritzBoxConfiguration config;
/**
* Bridge thing handler for updating thing status
*/
private final AVMFritzBaseBridgeHandler handler;
/**
* Shared instance of HTTP client for asynchronous calls
*/
private final HttpClient httpClient;
/**
* Current session ID
*/
private @Nullable String sid;
/**
* This method authenticates with the FRITZ!OS Web Interface and updates the session ID accordingly
*/
public void authenticate() {
sid = null;
String localPassword = config.password;
if (localPassword == null || localPassword.trim().isEmpty()) {
handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Please configure the password.");
return;
}
String loginXml = syncGet(getURL(WEBSERVICE_PATH, addSID("")));
if (loginXml == null) {
handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"FRITZ!Box does not respond.");
return;
}
Matcher sidmatch = SID_PATTERN.matcher(loginXml);
if (!sidmatch.find()) {
handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"FRITZ!Box does not respond with SID.");
return;
}
String localSid = sidmatch.group(1);
Matcher accmatch = ACCESS_PATTERN.matcher(loginXml);
if (accmatch.find()) {
if ("2".equals(accmatch.group(1))) {
sid = localSid;
handler.setStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
return;
}
}
Matcher challengematch = CHALLENGE_PATTERN.matcher(loginXml);
if (!challengematch.find()) {
handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"FRITZ!Box does not respond with challenge for authentication.");
return;
}
String challenge = challengematch.group(1);
String response = createResponse(challenge);
String localUser = config.user;
loginXml = syncGet(getURL(WEBSERVICE_PATH,
(localUser == null || localUser.isEmpty() ? "" : ("username=" + localUser + "&")) + "response="
+ response));
if (loginXml == null) {
handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"FRITZ!Box does not respond.");
return;
}
sidmatch = SID_PATTERN.matcher(loginXml);
if (!sidmatch.find()) {
handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"FRITZ!Box does not respond with SID.");
return;
}
localSid = sidmatch.group(1);
accmatch = ACCESS_PATTERN.matcher(loginXml);
if (accmatch.find()) {
if ("2".equals(accmatch.group(1))) {
sid = localSid;
handler.setStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
return;
}
}
handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "User "
+ (localUser == null ? "" : localUser) + " has no access to FRITZ!Box home automation functions.");
return;
}
/**
* Checks the authentication status of the web interface
*
* @return
*/
public boolean isAuthenticated() {
return sid != null;
}
/**
* Creates the proper response to a given challenge based on the password stored
*
* @param challenge Challenge string as returned by the FRITZ!OS login script
* @return Response to the challenge
*/
protected String createResponse(String challenge) {
String response = challenge.concat("-");
String handshake = response.concat(config.password);
MessageDigest md5;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
logger.error("This version of Java does not support MD5 hashing");
return "";
}
byte[] handshakeHash = md5.digest(handshake.getBytes(StandardCharsets.UTF_16LE));
for (byte handshakeByte : handshakeHash) {
response = response.concat(String.format("%02x", handshakeByte));
}
return response;
}
/**
* Constructor to set up interface
*
* @param config Bridge configuration
*/
public FritzAhaWebInterface(AVMFritzBoxConfiguration config, AVMFritzBaseBridgeHandler handler,
HttpClient httpClient) {
this.config = config;
this.handler = handler;
this.httpClient = httpClient;
authenticate();
logger.debug("Starting with SID {}", sid);
}
/**
* Constructs an URL from the stored information and a specified path
*
* @param path Path to include in URL
* @return URL
*/
public String getURL(String path) {
return config.protocol + "://" + config.ipAddress + (config.port == null ? "" : ":" + config.port) + "/" + path;
}
/**
* Constructs an URL from the stored information, a specified path and a specified argument string
*
* @param path Path to include in URL
* @param args String of arguments, in standard HTTP format (arg1=value1&arg2=value2&...)
* @return URL
*/
public String getURL(String path, String args) {
return getURL(args.isEmpty() ? path : path + "?" + args);
}
public String addSID(String path) {
if (sid == null) {
return path;
} else {
return (path.isEmpty() ? "" : path + "&") + "sid=" + sid;
}
}
/**
* Sends a HTTP GET request using the synchronous client
*
* @param path Path of the requested resource
* @return response
*/
public @Nullable String syncGet(String url) {
try {
ContentResponse contentResponse = httpClient.newRequest(url)
.timeout(config.syncTimeout, TimeUnit.MILLISECONDS).method(GET).send();
String content = contentResponse.getContentAsString();
logger.debug("GET response complete: {}", content);
return content;
} catch (ExecutionException | InterruptedException | TimeoutException e) {
logger.debug("response failed: {}", e.getLocalizedMessage(), e);
return null;
}
}
/**
* Sends a HTTP GET request using the asynchronous client
*
* @param path Path of the requested resource
* @param args Arguments for the request
* @param callback Callback to handle the response with
*/
public FritzAhaContentExchange asyncGet(String path, String args, FritzAhaCallback callback) {
if (!isAuthenticated()) {
authenticate();
}
FritzAhaContentExchange getExchange = new FritzAhaContentExchange(callback);
httpClient.newRequest(getURL(path, addSID(args))).method(GET).onResponseSuccess(getExchange)
.onResponseFailure(getExchange).send(getExchange);
return getExchange;
}
public FritzAhaContentExchange asyncGet(FritzAhaCallback callback) {
return asyncGet(callback.getPath(), callback.getArgs(), callback);
}
/**
* Sends a HTTP POST request using the asynchronous client
*
* @param path Path of the requested resource
* @param args Arguments for the request
* @param callback Callback to handle the response with
*/
public FritzAhaContentExchange asyncPost(String path, String args, FritzAhaCallback callback) {
if (!isAuthenticated()) {
authenticate();
}
FritzAhaContentExchange postExchange = new FritzAhaContentExchange(callback);
httpClient.newRequest(getURL(path)).timeout(config.asyncTimeout, TimeUnit.MILLISECONDS).method(POST)
.onResponseSuccess(postExchange).onResponseFailure(postExchange)
.content(new StringContentProvider(addSID(args), StandardCharsets.UTF_8)).send(postExchange);
return postExchange;
}
public FritzAhaContentExchange applyTemplate(String ain) {
FritzAhaApplyTemplateCallback callback = new FritzAhaApplyTemplateCallback(this, ain);
return asyncGet(callback);
}
public FritzAhaContentExchange setSwitch(String ain, boolean switchOn) {
FritzAhaSetSwitchCallback callback = new FritzAhaSetSwitchCallback(this, ain, switchOn);
return asyncGet(callback);
}
public FritzAhaContentExchange setSetTemp(String ain, BigDecimal temperature) {
FritzAhaSetHeatingTemperatureCallback callback = new FritzAhaSetHeatingTemperatureCallback(this, ain,
temperature);
return asyncGet(callback);
}
public FritzAhaContentExchange setBoostMode(String ain, long endTime) {
return setHeatingMode(ain, FritzAhaSetHeatingModeCallback.BOOST_COMMAND, endTime);
}
public FritzAhaContentExchange setWindowOpenMode(String ain, long endTime) {
return setHeatingMode(ain, FritzAhaSetHeatingModeCallback.WINDOW_OPEN_COMMAND, endTime);
}
private FritzAhaContentExchange setHeatingMode(String ain, String command, long endTime) {
FritzAhaSetHeatingModeCallback callback = new FritzAhaSetHeatingModeCallback(this, ain, command, endTime);
return asyncGet(callback);
}
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.hardware.callbacks;
import static org.eclipse.jetty.http.HttpMethod.GET;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.avmfritz.internal.hardware.FritzAhaWebInterface;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Callback implementation for applying templates.
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class FritzAhaApplyTemplateCallback extends FritzAhaReauthCallback {
private final Logger logger = LoggerFactory.getLogger(FritzAhaApplyTemplateCallback.class);
private static final String WEBSERVICE_COMMAND = "switchcmd=applytemplate";
private final String ain;
/**
* Constructor
*
* @param webInterface web interface to FRITZ!Box
* @param ain AIN of the template that should be applied
*/
public FritzAhaApplyTemplateCallback(FritzAhaWebInterface webInterface, String ain) {
super(WEBSERVICE_PATH, WEBSERVICE_COMMAND + "&ain=" + ain, webInterface, GET, 1);
this.ain = ain;
}
@Override
public void execute(int status, String response) {
super.execute(status, response);
if (isValidRequest()) {
logger.trace("Received response '{}' for item '{}'", response, ain);
}
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.hardware.callbacks;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Interface for callbacks in asynchronous requests.
*
* @author Robert Bausdorf - Initial contribution
*/
@NonNullByDefault
public interface FritzAhaCallback {
/**
* Runs callback code after response completion.
*/
void execute(int status, String response);
/**
* Get the URI path
*
* @return URI path as String
*/
public String getPath();
/**
* Get the query String
*
* @return Query string as String
*/
public String getArgs();
}

View File

@@ -0,0 +1,144 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.hardware.callbacks;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.http.HttpMethod;
import org.openhab.binding.avmfritz.internal.hardware.FritzAhaWebInterface;
/**
* Callback implementation for reauthorization and retry
*
* @author Robert Bausdorf, Christian Brauers - Initial contribution
*/
@NonNullByDefault
public class FritzAhaReauthCallback implements FritzAhaCallback {
public static final String WEBSERVICE_PATH = "webservices/homeautoswitch.lua";
/**
* Path to HTTP interface
*/
private final String path;
/**
* Arguments to use
*/
private final String args;
/**
* Web interface to use
*/
private final FritzAhaWebInterface webIface;
/**
* Method used
*/
private final HttpMethod httpMethod;
/**
* Number of remaining retries
*/
private int retries;
/**
* Callback to execute on next retry
*/
private FritzAhaCallback retryCallback;
/**
* Whether the request returned a valid response
*/
private boolean validRequest;
/**
* Returns whether the request returned a valid response
*
* @return Validity of response
*/
public boolean isValidRequest() {
return validRequest;
}
/**
* Returns whether there will be another retry on an invalid response
*
* @return true if there will be no more retries, false otherwise
*/
public boolean isFinalAttempt() {
return retries <= 0;
}
/**
* Sets different Callback to use on retry (initial value: same callback after decremented retry counter)
*
* @param retryCallback Callback to retry with
*/
public void setRetryCallback(FritzAhaCallback retryCallback) {
this.retryCallback = retryCallback;
}
@Override
public String getPath() {
return path;
}
@Override
public String getArgs() {
return args;
}
public FritzAhaWebInterface getWebIface() {
return webIface;
}
@Override
public void execute(int status, String response) {
if (status != 200 || "".equals(response) || ".".equals(response)) {
validRequest = false;
if (retries >= 1) {
webIface.authenticate();
retries--;
switch (httpMethod) {
case GET:
webIface.asyncGet(path, args, retryCallback);
break;
case POST:
webIface.asyncPost(path, args, retryCallback);
break;
default:
break;
}
}
} else {
validRequest = true;
}
}
/**
* Constructor for retryable authentication
*
* @param path
* Path to HTTP interface
* @param args
* Arguments to use
* @param webIface
* Web interface to use
* @param httpMethod
* Method used
* @param retries
* Number of retries
*/
public FritzAhaReauthCallback(String path, String args, FritzAhaWebInterface webIface, HttpMethod httpMethod,
int retries) {
this.path = path;
this.args = args;
this.webIface = webIface;
this.httpMethod = httpMethod;
this.retries = retries;
retryCallback = this;
}
}

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.hardware.callbacks;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.http.HttpMethod;
import org.openhab.binding.avmfritz.internal.hardware.FritzAhaWebInterface;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Callback implementation for updating heating modes
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class FritzAhaSetHeatingModeCallback extends FritzAhaReauthCallback {
private final Logger logger = LoggerFactory.getLogger(FritzAhaSetHeatingModeCallback.class);
public static final String BOOST_COMMAND = "sethkrboost";
public static final String WINDOW_OPEN_COMMAND = "sethkrwindowopen";
private final String ain;
/**
* Constructor
*
* @param webInterface connection to FRITZ!Box
* @param ain AIN of the device
* @param command the mode to set or deactivate
* @param endTimestamp the end timestamp in seconds, maximum allowed value is now + 24h in the future, 0 to
* deactivate the mode
*/
public FritzAhaSetHeatingModeCallback(FritzAhaWebInterface webInterface, String ain, String command,
long endTimestamp) {
super(WEBSERVICE_PATH, String.format("switchcmd=%s&ain=%s&endtimestamp=%d", command, ain, endTimestamp),
webInterface, HttpMethod.GET, 1);
this.ain = ain;
}
@Override
public void execute(int status, String response) {
super.execute(status, response);
if (isValidRequest()) {
logger.debug("Received response '{}' for item '{}'", response, ain);
}
}
}

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.hardware.callbacks;
import static org.eclipse.jetty.http.HttpMethod.GET;
import java.math.BigDecimal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.avmfritz.internal.hardware.FritzAhaWebInterface;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Callback implementation for updating heating values Supports reauthorization
*
* @author Christoph Weitkamp - Initial contribution
* @author Christoph Weitkamp - Added support for AVM FRITZ!DECT 300 and Comet
* DECT
*/
@NonNullByDefault
public class FritzAhaSetHeatingTemperatureCallback extends FritzAhaReauthCallback {
private final Logger logger = LoggerFactory.getLogger(FritzAhaSetHeatingTemperatureCallback.class);
private static final String WEBSERVICE_COMMAND = "switchcmd=sethkrtsoll";
private final String ain;
/**
* Constructor
*
* @param webIface Interface to FRITZ!Box
* @param ain AIN of the device that should be switched
* @param temperature New temperature
*/
public FritzAhaSetHeatingTemperatureCallback(FritzAhaWebInterface webIface, String ain, BigDecimal temperature) {
super(WEBSERVICE_PATH, WEBSERVICE_COMMAND + "&ain=" + ain + "&param=" + temperature, webIface, GET, 1);
this.ain = ain;
}
@Override
public void execute(int status, String response) {
super.execute(status, response);
if (isValidRequest()) {
logger.debug("Received response '{}' for item '{}'", response, ain);
}
}
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.hardware.callbacks;
import static org.eclipse.jetty.http.HttpMethod.GET;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.avmfritz.internal.hardware.FritzAhaWebInterface;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Callback implementation for updating switch states Supports reauthorization
*
* @author Robert Bausdorf - Initial contribution
*/
@NonNullByDefault
public class FritzAhaSetSwitchCallback extends FritzAhaReauthCallback {
private final Logger logger = LoggerFactory.getLogger(FritzAhaSetSwitchCallback.class);
private final String ain;
/**
* Constructor
*
* @param webIface Interface to FRITZ!Box
* @param ain AIN of the device that should be switched
* @param switchOn true - switch on, false - switch off
*/
public FritzAhaSetSwitchCallback(FritzAhaWebInterface webIface, String ain, boolean switchOn) {
super(WEBSERVICE_PATH, "switchcmd=" + (switchOn ? "setswitchon" : "setswitchoff") + "&ain=" + ain, webIface,
GET, 1);
this.ain = ain;
}
@Override
public void execute(int status, String response) {
super.execute(status, response);
if (isValidRequest()) {
logger.debug("Received response '{}' for item '{}'", response, ain);
}
}
}

View File

@@ -0,0 +1,83 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.hardware.callbacks;
import static org.eclipse.jetty.http.HttpMethod.GET;
import java.io.StringReader;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.avmfritz.internal.dto.DeviceListModel;
import org.openhab.binding.avmfritz.internal.handler.AVMFritzBaseBridgeHandler;
import org.openhab.binding.avmfritz.internal.hardware.FritzAhaWebInterface;
import org.openhab.binding.avmfritz.internal.util.JAXBUtils;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Callback implementation for updating multiple numbers decoded from a xml
* response. Supports reauthorization.
*
* @author Robert Bausdorf - Initial contribution
* @author Christoph Weitkamp - Added support for groups
*/
@NonNullByDefault
public class FritzAhaUpdateCallback extends FritzAhaReauthCallback {
private final Logger logger = LoggerFactory.getLogger(FritzAhaUpdateCallback.class);
private static final String WEBSERVICE_COMMAND = "switchcmd=getdevicelistinfos";
private final AVMFritzBaseBridgeHandler handler;
/**
* Constructor
*
* @param webIface Webinterface to FRITZ!Box
* @param handler Bridge handler that will update things.
*/
public FritzAhaUpdateCallback(FritzAhaWebInterface webIface, AVMFritzBaseBridgeHandler handler) {
super(WEBSERVICE_PATH, WEBSERVICE_COMMAND, webIface, GET, 1);
this.handler = handler;
}
@Override
public void execute(int status, String response) {
super.execute(status, response);
logger.trace("Received State response {}", response);
if (isValidRequest()) {
try {
Unmarshaller unmarshaller = JAXBUtils.JAXBCONTEXT_DEVICES.createUnmarshaller();
DeviceListModel model = (DeviceListModel) unmarshaller.unmarshal(new StringReader(response));
if (model != null) {
handler.onDeviceListAdded(model.getDevicelist());
} else {
logger.debug("no model in response");
}
handler.setStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
} catch (JAXBException e) {
logger.error("Exception creating Unmarshaller: {}", e.getLocalizedMessage(), e);
handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
e.getLocalizedMessage());
}
} else {
logger.debug("request is invalid: {}", status);
handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Request is invalid");
}
}
}

View File

@@ -0,0 +1,75 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.hardware.callbacks;
import static org.eclipse.jetty.http.HttpMethod.GET;
import java.io.StringReader;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.avmfritz.internal.dto.templates.TemplateListModel;
import org.openhab.binding.avmfritz.internal.handler.AVMFritzBaseBridgeHandler;
import org.openhab.binding.avmfritz.internal.hardware.FritzAhaWebInterface;
import org.openhab.binding.avmfritz.internal.util.JAXBUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Callback implementation for updating templates from a xml response.
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class FritzAhaUpdateTemplatesCallback extends FritzAhaReauthCallback {
private final Logger logger = LoggerFactory.getLogger(FritzAhaUpdateTemplatesCallback.class);
private static final String WEBSERVICE_COMMAND = "switchcmd=gettemplatelistinfos";
private final AVMFritzBaseBridgeHandler handler;
/**
* Constructor
*
* @param webInterface web interface to FRITZ!Box
* @param handler handler that will update things
*/
public FritzAhaUpdateTemplatesCallback(FritzAhaWebInterface webInterface, AVMFritzBaseBridgeHandler handler) {
super(WEBSERVICE_PATH, WEBSERVICE_COMMAND, webInterface, GET, 1);
this.handler = handler;
}
@Override
public void execute(int status, String response) {
super.execute(status, response);
logger.trace("Received response '{}'", response);
if (isValidRequest()) {
try {
Unmarshaller unmarshaller = JAXBUtils.JAXBCONTEXT_TEMPLATES.createUnmarshaller();
TemplateListModel model = (TemplateListModel) unmarshaller.unmarshal(new StringReader(response));
if (model != null) {
handler.addTemplateList(model.getTemplates());
} else {
logger.debug("no template in response");
}
} catch (JAXBException e) {
logger.error("Exception creating Unmarshaller: {}", e.getLocalizedMessage(), e);
}
} else {
logger.debug("request is invalid: {}", status);
}
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.util;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.avmfritz.internal.dto.DeviceListModel;
import org.openhab.binding.avmfritz.internal.dto.templates.TemplateListModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation for a static use of JAXBContext as singleton instance.
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class JAXBUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(JAXBUtils.class);
public static final @Nullable JAXBContext JAXBCONTEXT_DEVICES = initJAXBContextDevices();
public static final @Nullable JAXBContext JAXBCONTEXT_TEMPLATES = initJAXBContextTemplates();
private static @Nullable JAXBContext initJAXBContextDevices() {
try {
return JAXBContext.newInstance(DeviceListModel.class);
} catch (JAXBException e) {
LOGGER.error("Exception creating JAXBContext for devices: {}", e.getLocalizedMessage(), e);
return null;
}
}
private static @Nullable JAXBContext initJAXBContextTemplates() {
try {
return JAXBContext.newInstance(TemplateListModel.class);
} catch (JAXBException e) {
LOGGER.error("Exception creating JAXBContext for templates: {}", e.getLocalizedMessage(), e);
return null;
}
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="avmfritz" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>AVM FRITZ! Binding</name>
<description>This is the binding for AVM FRITZ! devices.</description>
<author>Robert Bausdorf</author>
</binding:binding>

View File

@@ -0,0 +1,145 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="bridge-type:avmfritz:fritzbox">
<parameter-group name="network">
<label>Network</label>
<description>Network settings.</description>
</parameter-group>
<parameter-group name="authentication">
<label>Authentication</label>
<description>Authentication settings.</description>
</parameter-group>
<parameter-group name="connection">
<label>Connection</label>
<description>Connection settings.</description>
</parameter-group>
<parameter name="ipAddress" type="text" required="true" groupName="network">
<context>network-address</context>
<label>IP Address</label>
<description>The local IP address or hostname of the FRITZ!Box.</description>
<default>fritz.box</default>
</parameter>
<parameter name="port" type="integer" min="1" max="65535" groupName="network">
<label>Port</label>
<description>Port of the FRITZ!Box.</description>
</parameter>
<parameter name="protocol" type="text" groupName="network">
<label>Protocol</label>
<description>Protocol to connect to the FRITZ!Box (http or https).</description>
<default>http</default>
<options>
<option value="http">HTTP</option>
<option value="https">HTTPS</option>
</options>
</parameter>
<parameter name="user" type="text" groupName="authentication">
<label>Username</label>
<description>User name which has HomeAuto permissions on the given FRITZ!Box.</description>
</parameter>
<parameter name="password" type="text" groupName="authentication">
<context>password</context>
<label>Password</label>
<description>Password to access the FRITZ!Box.</description>
</parameter>
<parameter name="pollingInterval" type="integer" min="5" max="60" unit="s" groupName="connection">
<label>Polling Interval</label>
<description>Interval polling the FRITZ!Box (in seconds).</description>
<default>15</default>
</parameter>
<parameter name="asyncTimeout" type="integer" min="1000" max="60000" unit="ms" groupName="connection">
<label>Async Timeout</label>
<description>Timeout for asynchronous connections (in milliseconds).</description>
<default>10000</default>
</parameter>
<parameter name="syncTimeout" type="integer" min="500" max="15000" unit="ms" groupName="connection">
<label>Timeout</label>
<description>Timeout for synchronous connections (in milliseconds).</description>
<default>2000</default>
</parameter>
</config-description>
<config-description uri="bridge-type:avmfritz:fritzpowerline">
<parameter-group name="network">
<label>Network</label>
<description>Network settings.</description>
</parameter-group>
<parameter-group name="authentication">
<label>Authentication</label>
<description>Authentication settings.</description>
</parameter-group>
<parameter-group name="connection">
<label>Connection</label>
<description>Connection settings.</description>
</parameter-group>
<parameter name="ain" type="text">
<label>AIN</label>
<description>The AHA id (AIN) that identifies one specific FRITZ! device.</description>
<advanced>true</advanced>
</parameter>
<parameter name="ipAddress" type="text" required="true" groupName="network">
<context>network-address</context>
<label>IP Address</label>
<description>The localIP address or hostname of the FRITZ!Powerline.</description>
<default>fritz.powerline</default>
</parameter>
<parameter name="port" type="integer" min="1" max="65535" groupName="network">
<label>Port</label>
<description>Port of the FRITZ!Powerline.</description>
</parameter>
<parameter name="protocol" type="text" groupName="network">
<label>Protocol</label>
<description>Protocol to connect to the FRITZ!Powerline (http or https).</description>
<default>http</default>
<options>
<option value="http">HTTP</option>
<option value="https">HTTPS</option>
</options>
</parameter>
<parameter name="password" type="text" required="true" groupName="authentication">
<context>password</context>
<label>Password</label>
<description>Password to access the FRITZ!Powerline.</description>
</parameter>
<parameter name="pollingInterval" type="integer" min="5" max="60" unit="s" groupName="connection">
<label>Polling Interval</label>
<description>Interval polling the FRITZ!Powerline (in seconds).</description>
<default>15</default>
</parameter>
<parameter name="asyncTimeout" type="integer" min="1000" max="60000" unit="ms" groupName="connection">
<label>Async Timeout</label>
<description>Timeout for asynchronous connections (in milliseconds).</description>
<default>10000</default>
</parameter>
<parameter name="syncTimeout" type="integer" min="500" max="15000" unit="ms" groupName="connection">
<label>Timeout</label>
<description>Timeout for synchronous connections (in milliseconds).</description>
<default>2000</default>
</parameter>
</config-description>
<config-description uri="thing-type:avmfritz:fritzdevice">
<parameter name="ain" type="text" required="true">
<label>AIN</label>
<description>The AHA id (AIN) that identifies one specific FRITZ! device.</description>
</parameter>
</config-description>
<config-description uri="thing-type:avmfritz:fritzgroup">
<parameter name="ain" type="text" required="true">
<label>AIN</label>
<description>The AHA id (AIN) that identifies one specific FRITZ! group.</description>
</parameter>
</config-description>
<config-description uri="channel-type:avmfritz:temperature">
<parameter name="offset" type="decimal" readOnly="true" unit="Cel">
<label>Temperature Offset</label>
<description>Current temperature offset (in °C).</description>
<default>0</default>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -0,0 +1,10 @@
# thing actions
setBoostModeModeActionLabel = Set Boost Mode
setBoostModeActionDescription = Activates the Boost mode of the heating thermostat.
setBoostModeDurationInputLabel = Duration
setBoostModeDurationInputDescription = Duration in seconds, min. 1, max. 86400, 0 for deactivation.
setWindowOpenModeActionLabel = Set Window Open Mode
setWindowOpenModeActionDescription = Activates the Window Open mode of the heating thermostat.
setWindowOpenModeDurationInputLabel = Duration
setWindowOpenModeDurationInputDescription = Duration in seconds, min. 1, max. 86400, 0 for deactivation.

View File

@@ -0,0 +1,208 @@
# binding
binding.avmfritz.name = AVM FRITZ! Binding
binding.avmfritz.description = Dieses Binding integriert AVM FRITZ! Geräte (z.B DECT 100/200/210/300/301, Comet DECT, Powerline 546E).
# bridge types
thing-type.avmfritz.fritzbox.description = FRITZ!Box
thing-type.avmfritz.FRITZ_Powerline_546E_Solo.description = FRITZ!Powerline 546E schaltbare Steckdose im stand-alone Modus. Dient zur Steuerung der integrierten Steckdose und liefert Daten wie z.B. Temperatur.
thing-type.avmfritz.HAN_FUN_CONTACT.label = HAN-FUN Kontakt
thing-type.avmfritz.HAN_FUN_CONTACT.description = HAN-FUN Kontakt (e.g. SmartHome Tür-/Fensterkontakt or SmartHome Bewegungsmelder).
thing-type.avmfritz.HAN_FUN_SWITCH.label = HAN-FUN Schalter
thing-type.avmfritz.HAN_FUN_SWITCH.description = HAN-FUN Schalter (e.g. SmartHome Wandtaster).
# bridge types config groups
bridge-type.config.avmfritz.fritzbox.group.network.label = Netzwerk
bridge-type.config.avmfritz.fritzbox.group.network.description = Einstellungen für das Netzwerk.
bridge-type.config.avmfritz.fritzbox.group.authentication.label = Authentifizierung
bridge-type.config.avmfritz.fritzbox.group.authentication.description = Einstellungen für die Authentifizierung.
bridge-type.config.avmfritz.fritzbox.group.connection.label = Verbindung
bridge-type.config.avmfritz.fritzbox.group.connection.description = Einstellungen für die Verbindung.
bridge-type.config.avmfritz.fritzpowerline.group.network.label = Netzwerk
bridge-type.config.avmfritz.fritzpowerline.group.network.description = Einstellungen für das Netzwerk.
bridge-type.config.avmfritz.fritzpowerline.group.authentication.label = Authentifizierung
bridge-type.config.avmfritz.fritzpowerline.group.authentication.description = Einstellungen für die Authentifizierung.
bridge-type.config.avmfritz.fritzpowerline.group.connection.label = Verbindung
bridge-type.config.avmfritz.fritzpowerline.group.connection.description = Einstellungen für die Verbindung.
# bridge types config
bridge-type.config.avmfritz.fritzbox.ipAddress.label = IP-Adresse
bridge-type.config.avmfritz.fritzbox.ipAddress.description = Lokale IP-Adresse oder Hostname der FRITZ!Box.
bridge-type.config.avmfritz.fritzbox.port.label = Port
bridge-type.config.avmfritz.fritzbox.port.description = Port der FRITZ!Box.
bridge-type.config.avmfritz.fritzbox.protocol.label = Protokoll
bridge-type.config.avmfritz.fritzbox.protocol.description = Protokoll für den Verbindungsaufbau zur FRITZ!Box (http or https).
bridge-type.config.avmfritz.fritzbox.password.label = Passwort
bridge-type.config.avmfritz.fritzbox.password.description = Passwort zur Authentifizierung an der FRITZ!Box.
bridge-type.config.avmfritz.fritzbox.user.label = Benutzer
bridge-type.config.avmfritz.fritzbox.user.description = Benutzer zur Authentifizierung an der FRITZ!Box.
bridge-type.config.avmfritz.fritzbox.pollingInterval.label = Abfrageintervall
bridge-type.config.avmfritz.fritzbox.pollingInterval.description = Intervall zur Abfrage der FRITZ!Box (in Sekunden).
bridge-type.config.avmfritz.fritzbox.asyncTimeout.label = Asynchroner Timeout
bridge-type.config.avmfritz.fritzbox.asyncTimeout.description = Timeout für asynchrone Abfragen der FRITZ!Box (in Millisekunden).
bridge-type.config.avmfritz.fritzbox.syncTimeout.label = Synchroner Timeout
bridge-type.config.avmfritz.fritzbox.syncTimeout.description = Timeout für synchrone Abfragen der FRITZ!Box (in Millisekunden).
bridge-type.config.avmfritz.fritzpowerline.ain.description = Die AHA ID (AIN) zur Identifikation des FRITZ!Powerline Gerätes.
bridge-type.config.avmfritz.fritzpowerline.ipAddress.label = IP-Adresse
bridge-type.config.avmfritz.fritzpowerline.ipAddress.description = Lokale IP-Adresse oder Hostname des FRITZ!Powerline Gerätes.
bridge-type.config.avmfritz.fritzpowerline.port.label = Port
bridge-type.config.avmfritz.fritzpowerline.port.description = Port des FRITZ!Powerline Gerätes.
bridge-type.config.avmfritz.fritzpowerline.protocol.label = Protokoll
bridge-type.config.avmfritz.fritzpowerline.protocol.description = Protokoll für den Verbindungsaufbau zum FRITZ!Powerline Gerät (http or https).
bridge-type.config.avmfritz.fritzpowerline.password.label = Passwort
bridge-type.config.avmfritz.fritzpowerline.password.description = Passwort zur Authentifizierung an dem FRITZ!Powerline Gerät.
bridge-type.config.avmfritz.fritzpowerline.pollingInterval.label = Abfrageintervall
bridge-type.config.avmfritz.fritzpowerline.pollingInterval.description = Intervall zur Abfrage des FRITZ!Powerline Gerät (in Sekunden).
bridge-type.config.avmfritz.fritzpowerline.asyncTimeout.label = Asynchroner Timeout
bridge-type.config.avmfritz.fritzpowerline.asyncTimeout.description = Timeout für asynchrone Abfragen des FRITZ!Powerline Gerät (in Millisekunden).
bridge-type.config.avmfritz.fritzpowerline.syncTimeout.label = Synchroner Timeout
bridge-type.config.avmfritz.fritzpowerline.syncTimeout.description = Timeout für synchrone Abfragen des FRITZ!Powerline Gerät (in Millisekunden).
# thing types
thing-type.avmfritz.FRITZ_DECT_Repeater_100.description = FRITZ!DECT Repeater 100 DECT Repeater. Liefert Daten wie z.B. Temperatur.
thing-type.avmfritz.FRITZ_DECT_200.description = FRITZ!DECT 200 schaltbare Steckdose. Dient zur Steuerung der integrierten Steckdose und liefert Daten wie z.B. Temperatur und Energie.
thing-type.avmfritz.FRITZ_DECT_210.description = FRITZ!DECT 210 schaltbare Steckdose. Dient zur Steuerung der integrierten Steckdose und liefert Daten wie z.B. Temperatur und Energie.
thing-type.avmfritz.FRITZ_DECT_300.description = FRITZ!DECT 300 Heizkörperregler. Dient zur Steuerung von Heizkörpern und liefert Daten wie z.B. Temperatur.
thing-type.avmfritz.FRITZ_DECT_301.description = FRITZ!DECT 301 Heizkörperregler. Dient zur Steuerung von Heizkörpern und liefert Daten wie z.B. Temperatur.
thing-type.avmfritz.Comet_DECT.description = Comet DECT Heizkörperregler. Dient zur Steuerung von Heizkörpern und liefert Daten wie z.B. Temperatur.
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_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
thing-type.avmfritz.FRITZ_GROUP_HEATING.label = Heizkörperregler
thing-type.avmfritz.FRITZ_GROUP_HEATING.description = Gruppe für Heizkörperregler. Dient zur Steuerung von Heizkörpern und liefert Daten wie z.B. Temperatur.
thing-type.avmfritz.FRITZ_GROUP_SWITCH.label = Schaltaktoren
thing-type.avmfritz.FRITZ_GROUP_SWITCH.description = Gruppe für Schaltaktoren und Energie Messgeräte. Dient zur Steuerung von Steckdosen und liefert Daten wie z.B. Energie.
# thing types config
thing-type.config.avmfritz.fritzdevice.ain.description = Die AHA ID (AIN) zur Identifikation des FRITZ! Gerätes.
thing-type.config.avmfritz.fritzgroup.ain.description = Die AHA ID (AIN) zur Identifikation der FRITZ! Gruppe.
# 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
channel-type.avmfritz.outgoing_call.description = Informationen zu angerufener und genutzter Telefonnummer.
channel-type.avmfritz.active_call.label = Aktiver Anruf
channel-type.avmfritz.active_call.description = Informationen zur aktuell bestehenden Verbindung.
channel-type.avmfritz.call_state.label = Anrufzustand
channel-type.avmfritz.call_state.description = Gibt an, ob derzeit eine Anrufaktivität stattfindet.
channel-type.avmfritz.call_state.state.option.IDLE = Inaktiv
channel-type.avmfritz.call_state.state.option.RINGING = Eingehener Anruf
channel-type.avmfritz.call_state.state.option.DIALING = Ausgehender Anruf
channel-type.avmfritz.call_state.state.option.ACTIVE = Verbunden
channel-type.avmfritz.mode.label = Modus des Gerätes
channel-type.avmfritz.mode.description = Zeigt den aktuellen Modus des Gerätes an (MANUAL/AUTOMATIC/VACATION).
channel-type.avmfritz.mode.state.option.MANUAL = Manuell
channel-type.avmfritz.mode.state.option.AUTOMATIC = Automatisch
channel-type.avmfritz.mode.state.option.VACATION = Urlaubsmodus
channel-type.avmfritz.locked.label = Externes Schalten
channel-type.avmfritz.locked.description = Zeigt an, ob das Schalten des Gerätes per Telefon, App oder Benutzeroberfläche aktiviert ist.
channel-type.avmfritz.device_locked.label = Tastensperre
channel-type.avmfritz.device_locked.description = Zeigt an, ob das Schalten per Taste am Gerät aktiviert ist.
channel-type.avmfritz.apply_template.label = Vorlage anwenden
channel-type.avmfritz.apply_template.description = Ermöglicht die Anwendung einer Vorlage.
channel-type.avmfritz.temperature.label = Temperatur
channel-type.avmfritz.temperature.description = Zeigt die aktuelle Temperatur an.
channel-type.avmfritz.energy.label = Gesamtverbrauch
channel-type.avmfritz.energy.description = Zeigt den akkumulierten Gesamtverbrauch an.
channel-type.avmfritz.power.label = Leistung
channel-type.avmfritz.power.description = Zeigt die aktuelle Leistung an.
channel-type.avmfritz.voltage.label = Spannung
channel-type.avmfritz.voltage.description = Zeigt die aktuelle Spannung an.
channel-type.avmfritz.outlet.label = Steckdose
channel-type.avmfritz.outlet.description = Ermöglicht die Steuerung der Steckdose (ON/OFF).
channel-type.avmfritz.actual_temp.label = Temperatur
channel-type.avmfritz.actual_temp.description = Zeigt die aktuell gemessene Temperatur des Heizkörperreglers an.
channel-type.avmfritz.set_temp.label = Solltemperatur
channel-type.avmfritz.set_temp.description = Ermöglicht die Steuerung der Solltemperatur des Heizkörperreglers.
channel-type.avmfritz.eco_temp.label = Absenktemperatur
channel-type.avmfritz.eco_temp.description = Zeigt die aktuell eingestellte Absenktemperatur des Heizkörperreglers an.
channel-type.avmfritz.comfort_temp.label = Komforttemperatur
channel-type.avmfritz.comfort_temp.description = Zeigt die aktuell eingestellte Komforttemperatur des Heizkörperreglers an.
channel-type.avmfritz.radiator_mode.label = Modus des Heizkörperreglers
channel-type.avmfritz.radiator_mode.description = Ermöglicht die Steuerung des aktuellen Modus des Heizkörperreglers (ON/OFF/COMFORT/ECO/BOOST/WINDOW_OPEN).
channel-type.avmfritz.radiator_mode.state.option.ON = An
channel-type.avmfritz.radiator_mode.state.option.OFF = Aus
channel-type.avmfritz.radiator_mode.state.option.COMFORT = Komfort
channel-type.avmfritz.radiator_mode.state.option.ECO = Absenk
channel-type.avmfritz.radiator_mode.state.option.BOOST = Boost
channel-type.avmfritz.radiator_mode.state.option.WINDOW_OPEN = Fenster-Auf
channel-type.avmfritz.next_change.label = Nächste Änderung
channel-type.avmfritz.next_change.description = Zeigt den Zeitpunkt der nächsten Änderung der Solltemperatur des Heizkörperreglers an.
channel-type.avmfritz.next_change.pattern = pattern = %1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS
channel-type.avmfritz.next_temp.label = Nächste Solltemperatur
channel-type.avmfritz.next_temp.description = Zeigt die nächste Solltemperatur des Heizkörperreglers an.
channel-type.avmfritz.contact_state.label = Tür-/Fenster-Zustand
channel-type.avmfritz.contact_state.description = Zeigt an, ob die Tür oder das Fester offen oder geschlossen ist (OPEN/CLOSED).
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.
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
# channel types config
channel-type.config.avmfritz.temperature.offset.label = Temperatur-Offset
channel-type.config.avmfritz.temperature.offset.description = Zeigt den aktuell eingestellten Temperatur-Offset (in °C) an.
# thing actions
setBoostModeModeActionLabel = Boost-Modus Aktivieren
setBoostModeActionDescription = Aktiviert den Boost-Modus des Heizkörperregler.
setBoostModeDurationInputLabel = Dauer
setBoostModeDurationInputDescription = Dauer in Sekunden, min. 1, max. 86400, 0 zur Deaktivierung.
setWindowOpenModeActionLabel = Fenster-Auf-Modus Aktivieren
setWindowOpenModeActionDescription = Aktiviert den Fenster-Auf-Modus des Heizkörperregler.
setWindowOpenModeDurationInputLabel = Dauer
setWindowOpenModeDurationInputDescription = Dauer in Sekunden, min. 1, max. 86400, 0 zur Deaktivierung.

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="avmfritz"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Supported FRITZ!Box and FRITZ!Powerline -->
<bridge-type id="fritzbox">
<label>FRITZ!Box</label>
<description>A FRITZ!Box router.</description>
<channels>
<channel id="incoming_call" typeId="incoming_call"/>
<channel id="outgoing_call" typeId="outgoing_call"/>
<channel id="active_call" typeId="active_call"/>
<channel id="call_state" typeId="call_state"/>
<channel id="apply_template" typeId="apply_template"/>
</channels>
<config-description-ref uri="bridge-type:avmfritz:fritzbox"/>
</bridge-type>
<bridge-type id="FRITZ_Powerline_546E_Solo">
<label>FRITZ!Powerline 546E</label>
<description>A FRITZ!Powerline 546E with switchable outlet in stand-alone mode.</description>
<channels>
<channel id="mode" typeId="mode"/>
<channel id="locked" typeId="locked"/>
<channel id="device_locked" typeId="device_locked"/>
<channel id="apply_template" typeId="apply_template"/>
<channel id="energy" typeId="energy"/>
<channel id="power" typeId="power"/>
<channel id="outlet" typeId="outlet"/>
</channels>
<config-description-ref uri="bridge-type:avmfritz:fritzpowerline"/>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,197 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="avmfritz"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Channel definitions of FRITZ!Box -->
<channel-type id="incoming_call">
<item-type>Call</item-type>
<label>Incoming Call</label>
<description>Details about incoming call.</description>
<state pattern="%1$s to %2$s" readOnly="true"/>
</channel-type>
<channel-type id="outgoing_call">
<item-type>Call</item-type>
<label>Outgoing Call</label>
<description>Details about outgoing call.</description>
<state pattern="%1$s to %2$s" readOnly="true"/>
</channel-type>
<channel-type id="active_call">
<item-type>Call</item-type>
<label>Active Call</label>
<description>Details about active call.</description>
<state pattern="%1$s" readOnly="true"/>
</channel-type>
<channel-type id="call_state">
<item-type>String</item-type>
<label>Call State</label>
<description>Details about current call state.</description>
<state readOnly="true">
<options>
<option value="IDLE">Idle</option>
<option value="RINGING">Ringing</option>
<option value="DIALING">Dialing</option>
<option value="ACTIVE">Active</option>
</options>
</state>
</channel-type>
<channel-type id="apply_template" advanced="true">
<item-type>String</item-type>
<label>Apply Template</label>
<description>Apply template for device(s).</description>
<state pattern="%s"/>
</channel-type>
<!-- Channel definitions of all FRITZ! devices -->
<channel-type id="mode">
<item-type>String</item-type>
<label>Mode</label>
<description>States the mode of the device (MANUAL/AUTOMATIC/VACATION).</description>
<state pattern="%s" readOnly="true">
<options>
<option value="MANUAL">Manual</option>
<option value="AUTOMATIC">Automatic</option>
<option value="VACATION">Vacation</option>
</options>
</state>
</channel-type>
<channel-type id="locked" advanced="true">
<item-type>Contact</item-type>
<label>Device Locked (external)</label>
<description>Device is locked for switching over external sources.</description>
<category>Contact</category>
<state pattern="%s" readOnly="true"/>
</channel-type>
<channel-type id="device_locked" advanced="true">
<item-type>Contact</item-type>
<label>Locked (manual)</label>
<description>Device is locked for switching by pressing the button on the device.</description>
<category>Contact</category>
<state pattern="%s" readOnly="true"/>
</channel-type>
<!-- Channel definitions of specific FRITZ! devices -->
<channel-type id="temperature">
<item-type>Number:Temperature</item-type>
<label>Current Temperature</label>
<description>Current measured temperature.</description>
<category>Temperature</category>
<state pattern="%.1f %unit%" readOnly="true"/>
<config-description-ref uri="channel-type:avmfritz:temperature"/>
</channel-type>
<channel-type id="energy">
<item-type>Number:Energy</item-type>
<label>Energy Consumption</label>
<description>Accumulated energy consumption.</description>
<category>Energy</category>
<state pattern="%.3f kWh" readOnly="true"/>
</channel-type>
<channel-type id="power">
<item-type>Number:Power</item-type>
<label>Power</label>
<description>Current power consumption.</description>
<category>Energy</category>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="voltage">
<item-type>Number:ElectricPotential</item-type>
<label>Voltage</label>
<description>Current voltage.</description>
<category>Energy</category>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="outlet">
<item-type>Switch</item-type>
<label>Outlet</label>
<description>Switched outlet (ON/OFF).</description>
<category>PowerOutlet</category>
</channel-type>
<channel-type id="actual_temp" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Current Temperature</label>
<description>Current measured temperature.</description>
<category>Temperature</category>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="set_temp">
<item-type>Number:Temperature</item-type>
<label>Setpoint Temperature</label>
<description>Thermostat Setpoint temperature.</description>
<category>Temperature</category>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="eco_temp">
<item-type>Number:Temperature</item-type>
<label>Eco Temperature</label>
<description>Thermostat Eco temperature.</description>
<category>Temperature</category>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="comfort_temp">
<item-type>Number:Temperature</item-type>
<label>Comfort Temperature</label>
<description>Thermostat Comfort temperature.</description>
<category>Temperature</category>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="radiator_mode">
<item-type>String</item-type>
<label>Radiator Mode</label>
<description>States the mode of the radiator (ON/OFF/COMFORT/ECO/BOOST/WINDOW_OPEN).</description>
<state pattern="%s">
<options>
<option value="ON">On</option>
<option value="OFF">Off</option>
<option value="COMFORT">Comfort</option>
<option value="ECO">Eco</option>
<option value="BOOST">Boost</option>
<option value="WINDOW_OPEN">Window Open</option>
</options>
</state>
</channel-type>
<channel-type id="next_change" advanced="true">
<item-type>DateTime</item-type>
<label>Next Setpoint Change</label>
<description>Next change of Setpoint Temperature.</description>
<category>Time</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="next_temp" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Next Setpoint Temperature</label>
<description>Next Setpoint Temperature.</description>
<category>Temperature</category>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="contact_state">
<item-type>Contact</item-type>
<label>Contact State</label>
<description>Contact state information (OPEN/CLOSED).</description>
<category>Contact</category>
<state pattern="%s" readOnly="true"/>
</channel-type>
<channel-type id="last_change">
<item-type>DateTime</item-type>
<label>Last Change</label>
<description>States the last time the button was pressed.</description>
<category>Time</category>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,300 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="avmfritz"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Supported FRITZ! devices and features -->
<thing-type id="FRITZ_DECT_400">
<supported-bridge-type-refs>
<bridge-type-ref id="fritzbox"/>
<bridge-type-ref id="FRITZ_Powerline_546E_Solo"/>
</supported-bridge-type-refs>
<label>FRITZ!DECT 400</label>
<description>FRITZ!DECT400 switch.</description>
<channels>
<channel id="press" typeId="system.button">
<label>Button press</label>
<description>Triggered SHORT_PRESSED or LONG_PRESSED when a button was pressed.</description>
</channel>
<channel id="last_change" typeId="last_change"/>
<channel id="battery_level" typeId="system.battery-level"/>
<channel id="battery_low" typeId="system.low-battery"/>
</channels>
<representation-property>ain</representation-property>
<config-description-ref uri="thing-type:avmfritz:fritzdevice"/>
</thing-type>
<thing-type id="Comet_DECT">
<supported-bridge-type-refs>
<bridge-type-ref id="fritzbox"/>
<bridge-type-ref id="FRITZ_Powerline_546E_Solo"/>
</supported-bridge-type-refs>
<label>Comet DECT</label>
<description>Comet DECT heating thermostat.</description>
<channels>
<channel id="mode" typeId="mode"/>
<channel id="locked" typeId="locked"/>
<channel id="device_locked" typeId="device_locked"/>
<channel id="temperature" typeId="temperature"/>
<channel id="actual_temp" typeId="actual_temp"/>
<channel id="set_temp" typeId="set_temp"/>
<channel id="eco_temp" typeId="eco_temp"/>
<channel id="comfort_temp" typeId="comfort_temp"/>
<channel id="radiator_mode" typeId="radiator_mode"/>
<channel id="next_change" typeId="next_change"/>
<channel id="next_temp" typeId="next_temp"/>
<channel id="battery_low" typeId="system.low-battery"/>
</channels>
<representation-property>ain</representation-property>
<config-description-ref uri="thing-type:avmfritz:fritzdevice"/>
</thing-type>
<thing-type id="FRITZ_DECT_301">
<supported-bridge-type-refs>
<bridge-type-ref id="fritzbox"/>
<bridge-type-ref id="FRITZ_Powerline_546E_Solo"/>
</supported-bridge-type-refs>
<label>FRITZ!DECT 301</label>
<description>FRITZ!DECT 301 heating thermostat.</description>
<channels>
<channel id="mode" typeId="mode"/>
<channel id="locked" typeId="locked"/>
<channel id="device_locked" typeId="device_locked"/>
<channel id="temperature" typeId="temperature"/>
<channel id="actual_temp" typeId="actual_temp"/>
<channel id="set_temp" typeId="set_temp"/>
<channel id="eco_temp" typeId="eco_temp"/>
<channel id="comfort_temp" typeId="comfort_temp"/>
<channel id="radiator_mode" typeId="radiator_mode"/>
<channel id="next_change" typeId="next_change"/>
<channel id="next_temp" typeId="next_temp"/>
<channel id="battery_low" typeId="system.low-battery"/>
</channels>
<representation-property>ain</representation-property>
<config-description-ref uri="thing-type:avmfritz:fritzdevice"/>
</thing-type>
<thing-type id="FRITZ_DECT_300">
<supported-bridge-type-refs>
<bridge-type-ref id="fritzbox"/>
<bridge-type-ref id="FRITZ_Powerline_546E_Solo"/>
</supported-bridge-type-refs>
<label>FRITZ!DECT 300</label>
<description>FRITZ!DECT 300 heating thermostat.</description>
<channels>
<channel id="mode" typeId="mode"/>
<channel id="locked" typeId="locked"/>
<channel id="device_locked" typeId="device_locked"/>
<channel id="temperature" typeId="temperature"/>
<channel id="actual_temp" typeId="actual_temp"/>
<channel id="set_temp" typeId="set_temp"/>
<channel id="eco_temp" typeId="eco_temp"/>
<channel id="comfort_temp" typeId="comfort_temp"/>
<channel id="radiator_mode" typeId="radiator_mode"/>
<channel id="next_change" typeId="next_change"/>
<channel id="next_temp" typeId="next_temp"/>
<channel id="battery_low" typeId="system.low-battery"/>
</channels>
<representation-property>ain</representation-property>
<config-description-ref uri="thing-type:avmfritz:fritzdevice"/>
</thing-type>
<thing-type id="FRITZ_DECT_210">
<supported-bridge-type-refs>
<bridge-type-ref id="fritzbox"/>
<bridge-type-ref id="FRITZ_Powerline_546E_Solo"/>
</supported-bridge-type-refs>
<label>FRITZ!DECT 210</label>
<description>FRITZ!DECT210 switchable outlet.</description>
<channels>
<channel id="mode" typeId="mode"/>
<channel id="locked" typeId="locked"/>
<channel id="device_locked" typeId="device_locked"/>
<channel id="temperature" typeId="temperature"/>
<channel id="energy" typeId="energy"/>
<channel id="power" typeId="power"/>
<channel id="outlet" typeId="outlet"/>
</channels>
<representation-property>ain</representation-property>
<config-description-ref uri="thing-type:avmfritz:fritzdevice"/>
</thing-type>
<thing-type id="FRITZ_DECT_200">
<supported-bridge-type-refs>
<bridge-type-ref id="fritzbox"/>
<bridge-type-ref id="FRITZ_Powerline_546E_Solo"/>
</supported-bridge-type-refs>
<label>FRITZ!DECT 200</label>
<description>FRITZ!DECT200 switchable outlet.</description>
<channels>
<channel id="mode" typeId="mode"/>
<channel id="locked" typeId="locked"/>
<channel id="device_locked" typeId="device_locked"/>
<channel id="temperature" typeId="temperature"/>
<channel id="energy" typeId="energy"/>
<channel id="power" typeId="power"/>
<channel id="outlet" typeId="outlet"/>
</channels>
<representation-property>ain</representation-property>
<config-description-ref uri="thing-type:avmfritz:fritzdevice"/>
</thing-type>
<thing-type id="FRITZ_Powerline_546E">
<supported-bridge-type-refs>
<bridge-type-ref id="fritzbox"/>
<bridge-type-ref id="FRITZ_Powerline_546E_Solo"/>
</supported-bridge-type-refs>
<label>FRITZ!Powerline 546E</label>
<description>FRITZ!Powerline 546E with switchable outlet.
</description>
<channels>
<channel id="mode" typeId="mode"/>
<channel id="locked" typeId="locked"/>
<channel id="device_locked" typeId="device_locked"/>
<channel id="energy" typeId="energy"/>
<channel id="power" typeId="power"/>
<channel id="outlet" typeId="outlet"/>
</channels>
<representation-property>ain</representation-property>
<config-description-ref uri="thing-type:avmfritz:fritzdevice"/>
</thing-type>
<thing-type id="FRITZ_DECT_Repeater_100">
<supported-bridge-type-refs>
<bridge-type-ref id="fritzbox"/>
<bridge-type-ref id="FRITZ_Powerline_546E_Solo"/>
</supported-bridge-type-refs>
<label>FRITZ!DECT Repeater 100</label>
<description>FRITZ!DECT Repeater 100 DECT repeater.</description>
<channels>
<channel typeId="temperature" id="temperature"/>
</channels>
<representation-property>ain</representation-property>
<config-description-ref uri="thing-type:avmfritz:fritzdevice"/>
</thing-type>
<thing-type id="HAN_FUN_CONTACT">
<supported-bridge-type-refs>
<bridge-type-ref id="fritzbox"/>
<bridge-type-ref id="FRITZ_Powerline_546E_Solo"/>
</supported-bridge-type-refs>
<label>HAN-FUN Contact</label>
<description>HAN-FUN contact (e.g. SmartHome Tür-/Fensterkontakt or SmartHome Bewegungsmelder).</description>
<channels>
<channel typeId="contact_state" id="contact_state"/>
</channels>
<representation-property>ain</representation-property>
<config-description-ref uri="thing-type:avmfritz:fritzdevice"/>
</thing-type>
<thing-type id="HAN_FUN_SWITCH">
<supported-bridge-type-refs>
<bridge-type-ref id="fritzbox"/>
<bridge-type-ref id="FRITZ_Powerline_546E_Solo"/>
</supported-bridge-type-refs>
<label>HAN-FUN Switch</label>
<description>HAN-FUN switch (e.g. SmartHome Wandtaster).</description>
<channels>
<channel id="press" typeId="system.rawbutton">
<label>Button Press</label>
<description>Triggered when a button was pressed.</description>
</channel>
<channel typeId="last_change" id="last_change"/>
</channels>
<representation-property>ain</representation-property>
<config-description-ref uri="thing-type:avmfritz:fritzdevice"/>
</thing-type>
<!-- Supported FRITZ! groups and features -->
<thing-type id="FRITZ_GROUP_HEATING">
<supported-bridge-type-refs>
<bridge-type-ref id="fritzbox"/>
<bridge-type-ref id="FRITZ_Powerline_546E_Solo"/>
</supported-bridge-type-refs>
<label>Heating Group</label>
<description>Group for heating thermostats.</description>
<channels>
<channel id="mode" typeId="mode"/>
<channel id="locked" typeId="locked"/>
<channel id="device_locked" typeId="device_locked"/>
<channel id="actual_temp" typeId="actual_temp"/>
<channel id="set_temp" typeId="set_temp"/>
<channel id="eco_temp" typeId="eco_temp"/>
<channel id="comfort_temp" typeId="comfort_temp"/>
<channel id="radiator_mode" typeId="radiator_mode"/>
<channel id="next_change" typeId="next_change"/>
<channel id="next_temp" typeId="next_temp"/>
<channel id="battery_low" typeId="system.low-battery"/>
</channels>
<representation-property>ain</representation-property>
<config-description-ref uri="thing-type:avmfritz:fritzgroup"/>
</thing-type>
<thing-type id="FRITZ_GROUP_SWITCH">
<supported-bridge-type-refs>
<bridge-type-ref id="fritzbox"/>
<bridge-type-ref id="FRITZ_Powerline_546E_Solo"/>
</supported-bridge-type-refs>
<label>Switch Group</label>
<description>Group for switchable outlets and power meters.</description>
<channels>
<channel id="mode" typeId="mode"/>
<channel id="locked" typeId="locked"/>
<channel id="device_locked" typeId="device_locked"/>
<channel id="energy" typeId="energy"/>
<channel id="power" typeId="power"/>
<channel id="outlet" typeId="outlet"/>
</channels>
<representation-property>ain</representation-property>
<config-description-ref uri="thing-type:avmfritz:fritzgroup"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,106 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.actions;
import static org.mockito.MockitoAnnotations.initMocks;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.openhab.binding.avmfritz.internal.handler.AVMFritzHeatingActionsHandler;
import org.openhab.core.thing.binding.ThingActions;
import org.openhab.core.thing.binding.ThingHandler;
/**
* Unit tests for {@link AVMFritzHeatingActions}.
*
* @author Christoph Weitkamp - Initial contribution
*/
public class AVMFritzHeatingActionsTest {
private final ThingActions thingActionsStub = new ThingActions() {
@Override
public void setThingHandler(ThingHandler handler) {
}
@Override
public ThingHandler getThingHandler() {
return null;
}
};
private @Mock AVMFritzHeatingActionsHandler heatingActionsHandler;
private AVMFritzHeatingActions heatingActions;
@Before
public void setUp() {
initMocks(this);
heatingActions = new AVMFritzHeatingActions();
}
@Test(expected = IllegalArgumentException.class)
public void testSetBoostModeThingActionsIsNull() {
AVMFritzHeatingActions.setBoostMode(null, Long.valueOf(5L));
}
@Test(expected = IllegalArgumentException.class)
public void testSetBoostModeThingActionsIsNotPushoverThingActions() {
AVMFritzHeatingActions.setBoostMode(thingActionsStub, Long.valueOf(5L));
}
@Test(expected = IllegalArgumentException.class)
public void testSetBoostModeThingHandlerIsNull() {
AVMFritzHeatingActions.setBoostMode(heatingActions, Long.valueOf(5L));
}
@Test(expected = IllegalArgumentException.class)
public void testSetBoostModeDurationNull() {
heatingActions.setThingHandler(heatingActionsHandler);
AVMFritzHeatingActions.setBoostMode(heatingActions, null);
}
@Test
public void testSetBoostMode() {
heatingActions.setThingHandler(heatingActionsHandler);
AVMFritzHeatingActions.setBoostMode(heatingActions, Long.valueOf(5L));
}
@Test(expected = IllegalArgumentException.class)
public void testSetWindowOpenModeThingActionsIsNull() {
AVMFritzHeatingActions.setWindowOpenMode(null, Long.valueOf(5L));
}
@Test(expected = IllegalArgumentException.class)
public void testSetWindowOpenModeThingActionsIsNotPushoverThingActions() {
AVMFritzHeatingActions.setWindowOpenMode(thingActionsStub, Long.valueOf(5L));
}
@Test(expected = IllegalArgumentException.class)
public void testSetWindowOpenModeThingHandlerIsNull() {
AVMFritzHeatingActions.setWindowOpenMode(heatingActions, Long.valueOf(5L));
}
@Test(expected = IllegalArgumentException.class)
public void testSetWindowOpenModeDurationNull() {
heatingActions.setThingHandler(heatingActionsHandler);
AVMFritzHeatingActions.setWindowOpenMode(heatingActions, null);
}
@Test
public void testSetWindowOpenMode() {
heatingActions.setThingHandler(heatingActionsHandler);
AVMFritzHeatingActions.setWindowOpenMode(heatingActions, Long.valueOf(5L));
}
}

View File

@@ -0,0 +1,574 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.dto;
import static org.junit.Assert.*;
import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.*;
import java.io.StringReader;
import java.math.BigDecimal;
import java.util.Optional;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.Before;
import org.junit.Test;
import org.openhab.binding.avmfritz.internal.util.JAXBUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Tests for {@link DeviceListModel}.
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class AVMFritzDeviceListModelTest {
private final Logger logger = LoggerFactory.getLogger(AVMFritzDeviceListModelTest.class);
private @NonNullByDefault({}) DeviceListModel devices;
@Before
public void setUp() {
//@formatter:off
String xml =
"<devicelist version=\"1\">" +
"<group identifier=\"F0:A3:7F-900\" id=\"20000\" functionbitmask=\"6784\" fwversion=\"1.0\" manufacturer=\"AVM\" productname=\"\"><present>1</present><name>Schlafzimmer</name><switch><state>1</state><mode>manuell</mode><lock>0</lock><devicelock>0</devicelock></switch><powermeter><voltage>230051</voltage><power>0</power><energy>2087</energy></powermeter><groupinfo><masterdeviceid>17</masterdeviceid><members>17,18</members></groupinfo></group>" +
"<group identifier=\"F0:A3:7F-901\" id=\"20001\" functionbitmask=\"4160\" fwversion=\"1.0\" manufacturer=\"AVM\" productname=\"\"><present>1</present><name>Schlafzimmer</name><temperature><celsius>220</celsius><offset>-10</offset></temperature><hkr><tist>44</tist><tsoll>42</tsoll><absenk>28</absenk><komfort>42</komfort><lock>1</lock><devicelock>1</devicelock><errorcode>0</errorcode><windowopenactiv>0</windowopenactiv><windowopenactiveendtime>0</windowopenactiveendtime><boostactive>0</boostactive><boostactiveendtime>0</boostactiveendtime><batterylow>0</batterylow><battery>100</battery><nextchange><endperiod>1484341200</endperiod><tchange>28</tchange></nextchange></hkr><groupinfo><masterdeviceid>0</masterdeviceid><members>20,21,22</members></groupinfo></group>" +
"<device identifier=\"08761 0000434\" id=\"17\" functionbitmask=\"2944\" fwversion=\"03.83\" manufacturer=\"AVM\" productname=\"FRITZ!DECT 200\"><present>1</present><name>FRITZ!DECT 200 #1</name><switch><state>1</state><mode>manuell</mode><lock>0</lock><devicelock>0</devicelock></switch><powermeter><voltage>230051</voltage><power>0</power><energy>2087</energy></powermeter><temperature><celsius>255</celsius><offset>0</offset></temperature></device>" +
"<device identifier=\"08761 0000438\" id=\"18\" functionbitmask=\"2944\" fwversion=\"03.83\" manufacturer=\"AVM\" productname=\"FRITZ!DECT 210\"><present>1</present><name>FRITZ!DECT 210 #8</name><switch><state>1</state><mode>manuell</mode><lock>0</lock><devicelock>0</devicelock></switch><powermeter><voltage>230051</voltage><power>0</power><energy>2087</energy></powermeter><temperature><celsius>255</celsius><offset>0</offset></temperature></device>" +
"<device identifier=\"08761 0000437\" id=\"20\" functionbitmask=\"320\" fwversion=\"03.50\" manufacturer=\"AVM\" productname=\"FRITZ!DECT 300\"><present>0</present><name>FRITZ!DECT 300 #1</name><temperature><celsius>220</celsius><offset>-10</offset></temperature><hkr><tist>44</tist><tsoll>42</tsoll><absenk>28</absenk><komfort>42</komfort><lock>1</lock><devicelock>1</devicelock><errorcode>0</errorcode><windowopenactiv>0</windowopenactiv><windowopenactiveendtime>0</windowopenactiveendtime><boostactive>0</boostactive><boostactiveendtime>0</boostactiveendtime><batterylow>0</batterylow><battery>100</battery><nextchange><endperiod>1484341200</endperiod><tchange>28</tchange></nextchange></hkr></device>" +
"<device identifier=\"08761 0000436\" id=\"21\" functionbitmask=\"320\" fwversion=\"03.50\" manufacturer=\"AVM\" productname=\"FRITZ!DECT 301\"><present>0</present><name>FRITZ!DECT 301 #1</name><temperature><celsius>220</celsius><offset>-10</offset></temperature><hkr><tist>44</tist><tsoll>42</tsoll><absenk>28</absenk><komfort>42</komfort><lock>1</lock><devicelock>1</devicelock><errorcode>0</errorcode><windowopenactiv>0</windowopenactiv><windowopenactiveendtime>0</windowopenactiveendtime><boostactive>0</boostactive><boostactiveendtime>0</boostactiveendtime><batterylow>0</batterylow><battery>100</battery><nextchange><endperiod>1484341200</endperiod><tchange>28</tchange></nextchange></hkr></device>" +
"<device identifier=\"08761 0000435\" id=\"22\" functionbitmask=\"320\" fwversion=\"03.50\" manufacturer=\"AVM\" productname=\"Comet DECT\"><present>0</present><name>Comet DECT #1</name><temperature><celsius>220</celsius><offset>-10</offset></temperature><hkr><tist>44</tist><tsoll>42</tsoll><absenk>28</absenk><komfort>42</komfort><lock>1</lock><devicelock>1</devicelock><errorcode>0</errorcode><windowopenactiv>0</windowopenactiv><windowopenactiveendtime>0</windowopenactiveendtime><boostactive>0</boostactive><boostactiveendtime>0</boostactiveendtime><batterylow>0</batterylow><battery>100</battery><nextchange><endperiod>1484341200</endperiod><tchange>28</tchange></nextchange></hkr></device>" +
"<device identifier=\"5C:49:79:F0:A3:84\" id=\"30\" functionbitmask=\"640\" fwversion=\"06.92\" manufacturer=\"AVM\" productname=\"FRITZ!Powerline 546E\"><present>1</present><name>FRITZ!Powerline 546E #1</name><switch><state>0</state><mode>manuell</mode><lock>0</lock><devicelock>1</devicelock></switch><powermeter><voltage>230051</voltage><power>0</power><energy>2087</energy></powermeter></device>" +
"<device identifier=\"08761 0000439\" id=\"40\" functionbitmask=\"1280\" fwversion=\"03.86\" manufacturer=\"AVM\" productname=\"FRITZ!DECT Repeater 100\"><present>1</present><name>FRITZ!DECT Repeater 100 #5</name><temperature><celsius>230</celsius><offset>0</offset></temperature></device>" +
"<device identifier=\"11934 0059978-1\" id=\"2000\" functionbitmask=\"8208\" fwversion=\"0.0\" manufacturer=\"0x0feb\" productname=\"HAN-FUN\"><present>0</present><name>HAN-FUN #2: Unit #2</name><etsiunitinfo><etsideviceid>406</etsideviceid><unittype>514</unittype><interfaces>256</interfaces></etsiunitinfo><alert><state>1</state></alert></device>" +
"<device identifier=\"11934 0059979-1\" id=\"2001\" functionbitmask=\"8200\" fwversion=\"0.0\" manufacturer=\"0x0feb\" productname=\"HAN-FUN\"><present>0</present><name>HAN-FUN #2: Unit #2</name><etsiunitinfo><etsideviceid>412</etsideviceid><unittype>273</unittype><interfaces>772</interfaces></etsiunitinfo><button><lastpressedtimestamp>1529590797</lastpressedtimestamp></button></device>" +
"<device identifier=\"13096 0007307\" id=\"29\" functionbitmask=\"32\" fwversion=\"04.90\" manufacturer=\"AVM\" productname=\"FRITZ!DECT 400\"><present>1</present><name>FRITZ!DECT 400 #14</name><battery>100</battery><batterylow>0</batterylow><button identifier=\"13096 0007307-0\" id=\"5000\"><name>FRITZ!DECT 400 #14: kurz</name><lastpressedtimestamp>1549195586</lastpressedtimestamp></button><button identifier=\"13096 0007307-9\" id=\"5001\"><name>FRITZ!DECT 400 #14: lang</name><lastpressedtimestamp>1549195595</lastpressedtimestamp></button></device>" +
"</devicelist>";
//@formatter:off
try {
Unmarshaller u = JAXBUtils.JAXBCONTEXT_DEVICES.createUnmarshaller();
devices = (DeviceListModel) u.unmarshal(new StringReader(xml));
} catch (JAXBException e) {
logger.error("Exception creating Unmarshaller: {}", e.getLocalizedMessage(), e);
}
}
@Test
public void validateDeviceListModel() {
assertNotNull(devices);
assertEquals(12, devices.getDevicelist().size());
assertEquals("1", devices.getXmlApiVersion());
}
@Test
public void validateDECTRepeater100Model() {
Optional<AVMFritzBaseModel> optionalDevice = findModelByIdentifier("087610000439");
assertTrue(optionalDevice.isPresent());
assertTrue(optionalDevice.get() instanceof DeviceModel);
DeviceModel device = (DeviceModel) optionalDevice.get();
assertEquals("FRITZ!DECT Repeater 100", device.getProductName());
assertEquals("087610000439", device.getIdentifier());
assertEquals("40", device.getDeviceId());
assertEquals("03.86", device.getFirmwareVersion());
assertEquals("AVM", device.getManufacturer());
assertEquals(1, device.getPresent());
assertEquals("FRITZ!DECT Repeater 100 #5", device.getName());
assertFalse(device.isButton());
assertFalse(device.isHANFUNButton());
assertFalse(device.isHANFUNAlarmSensor());
assertTrue(device.isDectRepeater());
assertFalse(device.isSwitchableOutlet());
assertTrue(device.isTempSensor());
assertFalse(device.isPowermeter());
assertFalse(device.isHeatingThermostat());
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 validateDECT200Model() {
Optional<AVMFritzBaseModel> optionalDevice = findModel("FRITZ!DECT 200");
assertTrue(optionalDevice.isPresent());
assertTrue(optionalDevice.get() instanceof DeviceModel);
DeviceModel device = (DeviceModel) optionalDevice.get();
assertEquals("FRITZ!DECT 200", device.getProductName());
assertEquals("087610000434", device.getIdentifier());
assertEquals("17", device.getDeviceId());
assertEquals("03.83", device.getFirmwareVersion());
assertEquals("AVM", device.getManufacturer());
assertEquals(1, device.getPresent());
assertEquals("FRITZ!DECT 200 #1", device.getName());
assertFalse(device.isButton());
assertFalse(device.isHANFUNButton());
assertFalse(device.isHANFUNAlarmSensor());
assertFalse(device.isDectRepeater());
assertTrue(device.isSwitchableOutlet());
assertTrue(device.isTempSensor());
assertTrue(device.isPowermeter());
assertFalse(device.isHeatingThermostat());
assertNotNull(device.getSwitch());
assertEquals(SwitchModel.ON, device.getSwitch().getState());
assertEquals(MODE_MANUAL, device.getSwitch().getMode());
assertEquals(BigDecimal.ZERO, device.getSwitch().getLock());
assertEquals(BigDecimal.ZERO, device.getSwitch().getDevicelock());
assertNotNull(device.getTemperature());
assertEquals(new BigDecimal("25.5"), device.getTemperature().getCelsius());
assertEquals(new BigDecimal("0.0"), device.getTemperature().getOffset());
validatePowerMeter(device.getPowermeter());
assertNull(device.getHkr());
}
@Test
public void validateDECT210Model() {
Optional<AVMFritzBaseModel> optionalDevice = findModel("FRITZ!DECT 210");
assertTrue(optionalDevice.isPresent());
assertTrue(optionalDevice.get() instanceof DeviceModel);
DeviceModel device = (DeviceModel) optionalDevice.get();
assertEquals("FRITZ!DECT 210", device.getProductName());
assertEquals("087610000438", device.getIdentifier());
assertEquals("18", device.getDeviceId());
assertEquals("03.83", device.getFirmwareVersion());
assertEquals("AVM", device.getManufacturer());
assertEquals(1, device.getPresent());
assertEquals("FRITZ!DECT 210 #8", device.getName());
assertFalse(device.isButton());
assertFalse(device.isHANFUNButton());
assertFalse(device.isHANFUNAlarmSensor());
assertFalse(device.isDectRepeater());
assertTrue(device.isSwitchableOutlet());
assertTrue(device.isTempSensor());
assertTrue(device.isPowermeter());
assertFalse(device.isHeatingThermostat());
assertNotNull(device.getSwitch());
assertEquals(SwitchModel.ON, device.getSwitch().getState());
assertEquals(MODE_MANUAL, device.getSwitch().getMode());
assertEquals(BigDecimal.ZERO, device.getSwitch().getLock());
assertEquals(BigDecimal.ZERO, device.getSwitch().getDevicelock());
assertNotNull(device.getTemperature());
assertEquals(new BigDecimal("25.5"), device.getTemperature().getCelsius());
assertEquals(new BigDecimal("0.0"), device.getTemperature().getOffset());
validatePowerMeter(device.getPowermeter());
assertNull(device.getHkr());
}
@Test
public void validateDECT300Model() {
Optional<AVMFritzBaseModel> optionalDevice = findModel("FRITZ!DECT 300");
assertTrue(optionalDevice.isPresent());
assertTrue(optionalDevice.get() instanceof DeviceModel);
DeviceModel device = (DeviceModel) optionalDevice.get();
assertEquals("FRITZ!DECT 300", device.getProductName());
assertEquals("087610000437", device.getIdentifier());
assertEquals("20", device.getDeviceId());
assertEquals("03.50", device.getFirmwareVersion());
assertEquals("AVM", device.getManufacturer());
assertEquals(0, device.getPresent());
assertEquals("FRITZ!DECT 300 #1", device.getName());
assertFalse(device.isButton());
assertFalse(device.isHANFUNButton());
assertFalse(device.isHANFUNAlarmSensor());
assertFalse(device.isDectRepeater());
assertFalse(device.isSwitchableOutlet());
assertTrue(device.isTempSensor());
assertFalse(device.isPowermeter());
assertTrue(device.isHeatingThermostat());
assertNull(device.getSwitch());
assertNotNull(device.getTemperature());
assertEquals(new BigDecimal("22.0"), device.getTemperature().getCelsius());
assertEquals(new BigDecimal("-1.0"), device.getTemperature().getOffset());
assertNull(device.getPowermeter());
validateHeatingModel(device.getHkr());
}
@Test
public void validateDECT301Model() {
Optional<AVMFritzBaseModel> optionalDevice = findModel("FRITZ!DECT 301");
assertTrue(optionalDevice.isPresent());
assertTrue(optionalDevice.get() instanceof DeviceModel);
DeviceModel device = (DeviceModel) optionalDevice.get();
assertEquals("FRITZ!DECT 301", device.getProductName());
assertEquals("087610000436", device.getIdentifier());
assertEquals("21", device.getDeviceId());
assertEquals("03.50", device.getFirmwareVersion());
assertEquals("AVM", device.getManufacturer());
assertEquals(0, device.getPresent());
assertEquals("FRITZ!DECT 301 #1", device.getName());
assertFalse(device.isButton());
assertFalse(device.isHANFUNButton());
assertFalse(device.isHANFUNAlarmSensor());
assertFalse(device.isDectRepeater());
assertFalse(device.isSwitchableOutlet());
assertTrue(device.isTempSensor());
assertFalse(device.isPowermeter());
assertTrue(device.isHeatingThermostat());
assertNull(device.getSwitch());
assertNotNull(device.getTemperature());
assertEquals(new BigDecimal("22.0"), device.getTemperature().getCelsius());
assertEquals(new BigDecimal("-1.0"), device.getTemperature().getOffset());
assertNull(device.getPowermeter());
validateHeatingModel(device.getHkr());
}
@Test
public void validateCometDECTModel() {
Optional<AVMFritzBaseModel> optionalDevice = findModel("Comet DECT");
assertTrue(optionalDevice.isPresent());
assertTrue(optionalDevice.get() instanceof DeviceModel);
DeviceModel device = (DeviceModel) optionalDevice.get();
assertEquals("Comet DECT", device.getProductName());
assertEquals("087610000435", device.getIdentifier());
assertEquals("22", device.getDeviceId());
assertEquals("03.50", device.getFirmwareVersion());
assertEquals("AVM", device.getManufacturer());
assertEquals(0, device.getPresent());
assertEquals("Comet DECT #1", device.getName());
assertFalse(device.isButton());
assertFalse(device.isHANFUNButton());
assertFalse(device.isHANFUNAlarmSensor());
assertFalse(device.isDectRepeater());
assertFalse(device.isSwitchableOutlet());
assertTrue(device.isTempSensor());
assertFalse(device.isPowermeter());
assertTrue(device.isHeatingThermostat());
assertNull(device.getSwitch());
assertNotNull(device.getTemperature());
assertEquals(new BigDecimal("22.0"), device.getTemperature().getCelsius());
assertEquals(new BigDecimal("-1.0"), device.getTemperature().getOffset());
assertNull(device.getPowermeter());
validateHeatingModel(device.getHkr());
}
@Test
public void validateDECT400Model() {
Optional<AVMFritzBaseModel> optionalDevice = findModelByIdentifier("130960007307");
assertTrue(optionalDevice.isPresent());
assertTrue(optionalDevice.get() instanceof DeviceModel);
DeviceModel device = (DeviceModel) optionalDevice.get();
assertEquals("FRITZ!DECT 400", device.getProductName());
assertEquals("130960007307", device.getIdentifier());
assertEquals("29", device.getDeviceId());
assertEquals("04.90", device.getFirmwareVersion());
assertEquals("AVM", device.getManufacturer());
assertEquals(1, device.getPresent());
assertEquals("FRITZ!DECT 400 #14", device.getName());
assertTrue(device.isButton());
assertFalse(device.isHANFUNButton());
assertFalse(device.isHANFUNAlarmSensor());
assertFalse(device.isDectRepeater());
assertFalse(device.isSwitchableOutlet());
assertFalse(device.isTempSensor());
assertFalse(device.isPowermeter());
assertFalse(device.isHeatingThermostat());
assertEquals(new BigDecimal("100"), device.getBattery());
assertEquals(BatteryModel.BATTERY_OFF, device.getBatterylow());
assertEquals(2, device.getButtons().size());
assertEquals("FRITZ!DECT 400 #14: kurz", device.getButtons().get(0).getName());
assertEquals(1549195586, device.getButtons().get(0).getLastpressedtimestamp());
assertEquals("FRITZ!DECT 400 #14: lang", device.getButtons().get(1).getName());
assertEquals(1549195595, device.getButtons().get(1).getLastpressedtimestamp());
assertNull(device.getAlert());
assertNull(device.getSwitch());
assertNull(device.getTemperature());
assertNull(device.getPowermeter());
assertNull(device.getHkr());
}
@Test
public void validatePowerline546EModel() {
Optional<AVMFritzBaseModel> optionalDevice = findModel("FRITZ!Powerline 546E");
assertTrue(optionalDevice.isPresent());
assertTrue(optionalDevice.get() instanceof DeviceModel);
DeviceModel device = (DeviceModel) optionalDevice.get();
assertEquals("FRITZ!Powerline 546E", device.getProductName());
assertEquals("5C:49:79:F0:A3:84", device.getIdentifier());
assertEquals("30", device.getDeviceId());
assertEquals("06.92", device.getFirmwareVersion());
assertEquals("AVM", device.getManufacturer());
assertEquals(1, device.getPresent());
assertEquals("FRITZ!Powerline 546E #1", device.getName());
assertFalse(device.isButton());
assertFalse(device.isHANFUNButton());
assertFalse(device.isHANFUNAlarmSensor());
assertFalse(device.isDectRepeater());
assertTrue(device.isSwitchableOutlet());
assertFalse(device.isTempSensor());
assertTrue(device.isPowermeter());
assertFalse(device.isHeatingThermostat());
assertNotNull(device.getSwitch());
assertEquals(SwitchModel.OFF, device.getSwitch().getState());
assertEquals(MODE_MANUAL, device.getSwitch().getMode());
assertEquals(BigDecimal.ZERO, device.getSwitch().getLock());
assertEquals(BigDecimal.ONE, device.getSwitch().getDevicelock());
assertNull(device.getTemperature());
validatePowerMeter(device.getPowermeter());
assertNull(device.getHkr());
}
@Test
public void validateHANFUNContactModel() {
Optional<AVMFritzBaseModel> optionalDevice = findModelByIdentifier("119340059978-1");
assertTrue(optionalDevice.isPresent());
assertTrue(optionalDevice.get() instanceof DeviceModel);
DeviceModel device = (DeviceModel) optionalDevice.get();
assertEquals("HAN-FUN", device.getProductName());
assertEquals("119340059978-1", device.getIdentifier());
assertEquals("2000", device.getDeviceId());
assertEquals("0.0", device.getFirmwareVersion());
assertEquals("0x0feb", device.getManufacturer());
assertEquals(0, device.getPresent());
assertEquals("HAN-FUN #2: Unit #2", device.getName());
assertFalse(device.isButton());
assertFalse(device.isHANFUNButton());
assertTrue(device.isHANFUNAlarmSensor());
assertFalse(device.isDectRepeater());
assertFalse(device.isSwitchableOutlet());
assertFalse(device.isTempSensor());
assertFalse(device.isPowermeter());
assertFalse(device.isHeatingThermostat());
assertTrue(device.getButtons().isEmpty());
assertNotNull(device.getAlert());
assertEquals(BigDecimal.ONE, device.getAlert().getState());
assertNull(device.getSwitch());
assertNull(device.getTemperature());
assertNull(device.getPowermeter());
assertNull(device.getHkr());
}
@Test
public void validateHANFUNSwitchModel() {
Optional<AVMFritzBaseModel> optionalDevice = findModelByIdentifier("119340059979-1");
assertTrue(optionalDevice.isPresent());
assertTrue(optionalDevice.get() instanceof DeviceModel);
DeviceModel device = (DeviceModel) optionalDevice.get();
assertEquals("HAN-FUN", device.getProductName());
assertEquals("119340059979-1", device.getIdentifier());
assertEquals("2001", device.getDeviceId());
assertEquals("0.0", device.getFirmwareVersion());
assertEquals("0x0feb", device.getManufacturer());
assertEquals(0, device.getPresent());
assertEquals("HAN-FUN #2: Unit #2", device.getName());
assertFalse(device.isButton());
assertTrue(device.isHANFUNButton());
assertFalse(device.isHANFUNAlarmSensor());
assertFalse(device.isDectRepeater());
assertFalse(device.isSwitchableOutlet());
assertFalse(device.isTempSensor());
assertFalse(device.isPowermeter());
assertFalse(device.isHeatingThermostat());
assertEquals(1, device.getButtons().size());
assertEquals(1529590797, device.getButtons().get(0).getLastpressedtimestamp());
assertNull(device.getAlert());
assertNull(device.getSwitch());
assertNull(device.getTemperature());
assertNull(device.getPowermeter());
assertNull(device.getHkr());
}
@Test
public void validateHeatingGroupModel() {
Optional<AVMFritzBaseModel> optionalGroup = findModelByIdentifier("F0:A3:7F-901");
assertTrue(optionalGroup.isPresent());
assertTrue(optionalGroup.get() instanceof GroupModel);
GroupModel group = (GroupModel) optionalGroup.get();
assertEquals("", group.getProductName());
assertEquals("F0:A3:7F-901", group.getIdentifier());
assertEquals("20001", group.getDeviceId());
assertEquals("1.0", group.getFirmwareVersion());
assertEquals("AVM", group.getManufacturer());
assertEquals(1, group.getPresent());
assertEquals("Schlafzimmer", group.getName());
assertFalse(group.isButton());
assertFalse(group.isHANFUNButton());
assertFalse(group.isHANFUNAlarmSensor());
assertFalse(group.isDectRepeater());
assertFalse(group.isSwitchableOutlet());
assertFalse(group.isTempSensor());
assertFalse(group.isPowermeter());
assertTrue(group.isHeatingThermostat());
assertNull(group.getSwitch());
assertNull(group.getPowermeter());
validateHeatingModel(group.getHkr());
assertNotNull(group.getGroupinfo());
assertEquals("0", group.getGroupinfo().getMasterdeviceid());
assertEquals("20,21,22", group.getGroupinfo().getMembers());
}
@Test
public void validateSwitchGroupModel() {
Optional<AVMFritzBaseModel> optionalGroup = findModelByIdentifier("F0:A3:7F-900");
assertTrue(optionalGroup.isPresent());
assertTrue(optionalGroup.get() instanceof GroupModel);
GroupModel group = (GroupModel) optionalGroup.get();
assertEquals("", group.getProductName());
assertEquals("F0:A3:7F-900", group.getIdentifier());
assertEquals("20000", group.getDeviceId());
assertEquals("1.0", group.getFirmwareVersion());
assertEquals("AVM", group.getManufacturer());
assertEquals(1, group.getPresent());
assertEquals("Schlafzimmer", group.getName());
assertFalse(group.isButton());
assertFalse(group.isHANFUNButton());
assertFalse(group.isHANFUNAlarmSensor());
assertFalse(group.isDectRepeater());
assertTrue(group.isSwitchableOutlet());
assertFalse(group.isTempSensor());
assertTrue(group.isPowermeter());
assertFalse(group.isHeatingThermostat());
assertNotNull(group.getSwitch());
assertEquals(SwitchModel.ON, group.getSwitch().getState());
assertEquals(MODE_MANUAL, group.getSwitch().getMode());
assertEquals(BigDecimal.ZERO, group.getSwitch().getLock());
assertEquals(BigDecimal.ZERO, group.getSwitch().getDevicelock());
validatePowerMeter(group.getPowermeter());
assertNull(group.getHkr());
assertNotNull(group.getGroupinfo());
assertEquals("17", group.getGroupinfo().getMasterdeviceid());
assertEquals("17,18", group.getGroupinfo().getMembers());
}
private Optional<AVMFritzBaseModel> findModel(String name) {
return devices.getDevicelist().stream().filter(it -> name.equals(it.getProductName())).findFirst();
}
private Optional<AVMFritzBaseModel> findModelByIdentifier(String identifier) {
return devices.getDevicelist().stream().filter(it -> identifier.equals(it.getIdentifier())).findFirst();
}
private void validatePowerMeter(PowerMeterModel model) {
assertNotNull(model);
assertEquals(new BigDecimal("230.051"), model.getVoltage());
assertEquals(new BigDecimal("0.000"), model.getPower());
assertEquals(new BigDecimal("2087"), model.getEnergy());
}
private void validateHeatingModel(HeatingModel model) {
assertNotNull(model);
assertEquals(new BigDecimal(44), model.getTist());
assertEquals(new BigDecimal(42), model.getTsoll());
assertEquals(new BigDecimal(28), model.getAbsenk());
assertEquals(new BigDecimal(42), model.getKomfort());
assertEquals(BigDecimal.ONE, model.getLock());
assertEquals(BigDecimal.ONE, model.getDevicelock());
assertEquals(BigDecimal.ZERO, model.getWindowopenactiv());
assertEquals(BigDecimal.ZERO, model.getBoostactive());
assertEquals(new BigDecimal("100"), model.getBattery());
assertEquals(BatteryModel.BATTERY_OFF, model.getBatterylow());
assertEquals(MODE_AUTO, model.getMode());
assertEquals(MODE_COMFORT, model.getRadiatorMode());
assertNotNull(model.getNextchange());
assertEquals(1484341200, model.getNextchange().getEndperiod());
assertEquals(new BigDecimal(28), model.getNextchange().getTchange());
}
}

View File

@@ -0,0 +1,98 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.dto;
import static org.junit.Assert.*;
import java.io.StringReader;
import java.util.Optional;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.Before;
import org.junit.Test;
import org.openhab.binding.avmfritz.internal.dto.templates.TemplateListModel;
import org.openhab.binding.avmfritz.internal.dto.templates.TemplateModel;
import org.openhab.binding.avmfritz.internal.util.JAXBUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Tests for {@link TemplateListModel}.
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class AVMFritzTemplateListModelTest {
private final Logger logger = LoggerFactory.getLogger(AVMFritzTemplateListModelTest.class);
private @NonNullByDefault({}) TemplateListModel templates;
@Before
public void setUp() {
//@formatter:off
String xml =
"<templatelist version=\"1\">" +
"<template identifier=\"tmpXXXXX-39DC738C5\" id=\"30103\" functionbitmask=\"6784\" applymask=\"64\"><name>Test template #1</name><devices><device identifier=\"YY:5D:AA-900\" /><device identifier=\"XX:5D:AA-900\" /></devices><applymask><relay_automatic /></applymask></template>" +
"<template identifier=\"tmpXXXXX-39722FC0F\" id=\"30003\" functionbitmask=\"6784\" applymask=\"64\"><name>Test template #2</name><devices><device identifier=\"YY:5D:AA-900\" /></devices><applymask><relay_automatic /></applymask></template>" +
"</templatelist>";
//@formatter:off
try {
Unmarshaller u = JAXBUtils.JAXBCONTEXT_TEMPLATES.createUnmarshaller();
templates = (TemplateListModel) u.unmarshal(new StringReader(xml));
} catch (JAXBException e) {
logger.error("Exception creating Unmarshaller: {}", e.getLocalizedMessage(), e);
}
}
@Test
public void validateDeviceListModel() {
assertNotNull(templates);
assertEquals(2, templates.getTemplates().size());
assertEquals("1", templates.getVersion());
}
@Test
public void validateTemplate1() {
Optional<TemplateModel> optionalTemplate = findModelByIdentifier("tmpXXXXX-39DC738C5");
assertTrue(optionalTemplate.isPresent());
assertTrue(optionalTemplate.get() instanceof TemplateModel);
TemplateModel template = optionalTemplate.get();
assertEquals("30103", template.getTemplateId());
assertEquals("Test template #1", template.getName());
assertEquals(2, template.getDeviceList().getDevices().size());
}
@Test
public void validateTemplate2() {
Optional<TemplateModel> optionalTemplate = findModelByIdentifier("tmpXXXXX-39722FC0F");
assertTrue(optionalTemplate.isPresent());
assertTrue(optionalTemplate.get() instanceof TemplateModel);
TemplateModel template = optionalTemplate.get();
assertEquals("30003", template.getTemplateId());
assertEquals("Test template #2", template.getName());
assertEquals(1, template.getDeviceList().getDevices().size());
}
private Optional<TemplateModel> findModelByIdentifier(String identifier) {
return templates.getTemplates().stream().filter(it -> identifier.equals(it.getIdentifier())).findFirst();
}
}

View File

@@ -0,0 +1,63 @@
/**
* Copyright (c) 2010-2020 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.avmfritz.internal.dto;
import static org.junit.Assert.assertEquals;
import java.math.BigDecimal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.Test;
/**
* Tests for {@link HeatingModel} methods.
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class HeatingModelTest {
private static final BigDecimal BIGDECIMAL_FOURTEEN = new BigDecimal("14.0");
private static final BigDecimal BIGDECIMAL_FOURTEEN_POINT_FIVE = new BigDecimal("14.5");
@Test
public void validateTemperatureConversionFromCelsius() {
assertEquals(BigDecimal.ZERO, HeatingModel.fromCelsius(null));
assertEquals(HeatingModel.TEMP_FRITZ_MIN, HeatingModel.fromCelsius(BigDecimal.ONE));
assertEquals(HeatingModel.TEMP_FRITZ_MIN, HeatingModel.fromCelsius(new BigDecimal("7.5")));
assertEquals(new BigDecimal("16.00"), HeatingModel.fromCelsius(HeatingModel.TEMP_CELSIUS_MIN));
assertEquals(new BigDecimal("28.00"), HeatingModel.fromCelsius(BIGDECIMAL_FOURTEEN));
assertEquals(new BigDecimal("29.00"), HeatingModel.fromCelsius(BIGDECIMAL_FOURTEEN_POINT_FIVE));
assertEquals(new BigDecimal("56.00"), HeatingModel.fromCelsius(HeatingModel.TEMP_CELSIUS_MAX));
assertEquals(HeatingModel.TEMP_FRITZ_MAX, HeatingModel.fromCelsius(new BigDecimal("28.5")));
assertEquals(HeatingModel.TEMP_FRITZ_MAX, HeatingModel.fromCelsius(new BigDecimal("35")));
}
@Test
public void validateTemperatureConversionToCelsius() {
assertEquals(BigDecimal.ZERO, HeatingModel.toCelsius(null));
assertEquals(BIGDECIMAL_FOURTEEN, HeatingModel.toCelsius(new BigDecimal("28")));
assertEquals(BIGDECIMAL_FOURTEEN_POINT_FIVE, HeatingModel.toCelsius(new BigDecimal("29")));
assertEquals(new BigDecimal("6.0"), HeatingModel.toCelsius(HeatingModel.TEMP_FRITZ_OFF));
assertEquals(new BigDecimal("30.0"), HeatingModel.toCelsius(HeatingModel.TEMP_FRITZ_ON));
}
@Test
public void validateTemperatureNormalization() {
assertEquals(BIGDECIMAL_FOURTEEN, HeatingModel.normalizeCelsius(BIGDECIMAL_FOURTEEN));
assertEquals(BIGDECIMAL_FOURTEEN, HeatingModel.normalizeCelsius(new BigDecimal("13.9")));
assertEquals(BIGDECIMAL_FOURTEEN, HeatingModel.normalizeCelsius(new BigDecimal("14.1")));
assertEquals(BIGDECIMAL_FOURTEEN_POINT_FIVE, HeatingModel.normalizeCelsius(new BigDecimal("14.4")));
assertEquals(BIGDECIMAL_FOURTEEN_POINT_FIVE, HeatingModel.normalizeCelsius(new BigDecimal("14.6")));
}
}