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,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>