mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[tr064] Initial contribution (#8523)
* Initial contribution Signed-off-by: Jan N. Klug <jan.n.klug@rub.de>
This commit is contained in:
parent
5c4c89a0c9
commit
a3d33fa60b
@ -245,6 +245,7 @@
|
||||
/bundles/org.openhab.binding.tibber/ @kjoglum
|
||||
/bundles/org.openhab.binding.touchwand /@roieg
|
||||
/bundles/org.openhab.binding.tplinksmarthome/ @Hilbrand
|
||||
/bundles/org.openhab.binding.tr064/ @J-N-K
|
||||
/bundles/org.openhab.binding.tradfri/ @cweitkamp @kaikreuzer
|
||||
/bundles/org.openhab.binding.unifi/ @mgbowman
|
||||
/bundles/org.openhab.binding.unifiedremote/ @GiviMAD
|
||||
|
@ -1211,6 +1211,11 @@
|
||||
<artifactId>org.openhab.binding.tplinksmarthome</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.tr064</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.tradfri</artifactId>
|
||||
|
13
bundles/org.openhab.binding.tr064/NOTICE
Normal file
13
bundles/org.openhab.binding.tr064/NOTICE
Normal file
@ -0,0 +1,13 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab2-addons
|
119
bundles/org.openhab.binding.tr064/README.md
Normal file
119
bundles/org.openhab.binding.tr064/README.md
Normal file
@ -0,0 +1,119 @@
|
||||
# TR-064 Binding
|
||||
|
||||
This binding brings support for internet gateway devices that support the TR-064 protocol.
|
||||
It can be used to gather information from the device and/or re-configure it.
|
||||
|
||||
## Supported Things
|
||||
|
||||
Four thing types are supported:
|
||||
|
||||
- `generic`: the internet gateway device itself (generic device)
|
||||
- `fritzbox`: similar to `generic` with extensions for AVM FritzBox devices
|
||||
- `subDevice`: a sub-device of a `rootDevice` (e.g. a WAN interface)
|
||||
- `subDeviceLan`: a special type of sub-device that supports MAC-detection
|
||||
|
||||
## Discovery
|
||||
|
||||
The gateway device needs to be added manually.
|
||||
After that, sub-devices are detected automatically.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
All thing types have a `refresh` parameter.
|
||||
It sets the refresh-interval in seconds for each device channel.
|
||||
The default value is 60.
|
||||
|
||||
### `generic`, `fritzbox`
|
||||
|
||||
The `host` parameter is required to communicate with the device.
|
||||
It can be a hostname or an IP address.
|
||||
|
||||
For accessing the device you need to supply credentials.
|
||||
If you only configured password authentication for your device, the `user` parameter must be skipped and it will default to `dslf-config`.
|
||||
The second credential parameter is `password`, which is mandatory.
|
||||
For security reasons it is highly recommended to set both, username and password.
|
||||
|
||||
### `fritzbox`
|
||||
|
||||
All additional parameters for `fritzbox` devices (i.e. except those that are shared with `generic`) are advanced parameters.
|
||||
|
||||
One or more TAM (telephone answering machines) are supported by most devices.
|
||||
By setting the `tamIndices` parameter you can instruct the binding to add channels for these devices to the thing.
|
||||
Values start with `0`.
|
||||
This is an optional parameter and multiple values are allowed.
|
||||
|
||||
Most devices allow to configure call deflections.
|
||||
If the `callDeflectionIndices` parameter is set, channels for the status of the pre-configured call deflections are added.
|
||||
Values start with `0`, including the number of "Call Blocks" (two configured call-blocks -> first deflection is `2`).
|
||||
This is an optional parameter and multiple values are allowed.
|
||||
|
||||
Most devices support call lists.
|
||||
The binding can analyze these call lists and provide channels for the number of missed calls, inbound calls, outbound calls and rejected (blocked) calls.
|
||||
The days for which this analysis takes place can be controlled with the `missedCallDays`, `rejectedCallDays`, `inboundCallDays` and `outboundCallDays`
|
||||
This is an optional parameter and multiple values are allowed.
|
||||
|
||||
Since FritzOS! 7.20 WAN access of local devices can be controlled by their IPs.
|
||||
If the `wanBlockIPs` parameter is set, a channel for each IP is created to block/unblock WAN access for this IP.
|
||||
Values need to be IPv4 addresses in the format `a.b.c.d`.
|
||||
This is an optional parameter and multiple values are allowed.
|
||||
|
||||
If the `PHONEBOOK` profile shall be used, it is necessary to retrieve the phonebooks from the FritzBox.
|
||||
The `phonebookInterval` is uses to set the refresh cycle for phonebooks.
|
||||
|
||||
### `subdevice`, `subdeviceLan`
|
||||
|
||||
Besides the bridge that the thing is attached to, sub-devices have a `uuid` parameter.
|
||||
This is the UUID/UDN of the device and a mandatory parameter.
|
||||
Since the value can only be determined by examining the SCPD of the root device, the simplest way to get hold of them is through auto-discovery.
|
||||
|
||||
For `subdeviceLan` devices (type is detected automatically during discovery) the parameter `macOnline` can be defined.
|
||||
It adds a channel for each MAC (format 11:11:11:11:11:11) that shows the online status of the respective device.
|
||||
This is an optional parameter and multiple values are allowed.
|
||||
|
||||
## Channels
|
||||
|
||||
| channel | item-type | advanced | description |
|
||||
|----------------------------|---------------------------|:--------:|----------------------------------------------------------------|
|
||||
| `callDeflectionEnable` | `Switch` | | Enable/Disable the call deflection setup with the given index. |
|
||||
| `deviceLog` | `String` | x | A string containing the last log messages. |
|
||||
| `dslCRCErrors` | `Number:Dimensionless` | x | DSL CRC Errors |
|
||||
| `dslDownstreamNoiseMargin` | `Number:Dimensionless` | x | DSL Downstream Noise Margin |
|
||||
| `dslDownstreamNoiseMargin` | `Number:Dimensionless` | x | DSL Downstream Attenuation |
|
||||
| `dslEnable` | `Switch` | | DSL Enable |
|
||||
| `dslFECErrors` | `Number:Dimensionless` | x | DSL FEC Errors |
|
||||
| `dslHECErrors` | `Number:Dimensionless` | x | DSL HEC Errors |
|
||||
| `dslStatus` | `Switch` | | DSL Status |
|
||||
| `dslUpstreamNoiseMargin` | `Number:Dimensionless` | x | DSL Upstream Noise Margin |
|
||||
| `dslUpstreamNoiseMargin` | `Number:Dimensionless` | x | DSL Upstream Attenuation |
|
||||
| `inboundCalls` | `Number` | x | Number of inbound calls within the given number of days. |
|
||||
| `macOnline` | `Switch` | x | Online status of the device with the given MAC |
|
||||
| `missedCalls` | `Number` | | Number of missed calls within the given number of days. |
|
||||
| `outboundCalls` | `Number` | x | Number of outbound calls within the given number of days. |
|
||||
| `reboot` | `Switch` | | Reboot |
|
||||
| `rejectedCalls` | `Number` | x | Number of rejected calls within the given number of days. |
|
||||
| `securityPort` | `Number` | x | The port for connecting via HTTPS to the TR-064 service. |
|
||||
| `tamEnable` | `Switch` | | Enable/Disable the answering machine with the given index. |
|
||||
| `tamNewMessages` | `Number` | | The number of new messages of the given answering machine. |
|
||||
| `uptime` | `Number:Time` | | Uptime |
|
||||
| `wanAccessType` | `String` | x | Access Type |
|
||||
| `wanConnectionStatus` | `String` | | Connection Status |
|
||||
| `wanIpAddress` | `String` | x | WAN IP Address |
|
||||
| `wanMaxDownstreamRate` | `Number:DataTransferRate` | x | Max. Downstream Rate |
|
||||
| `wanMaxUpstreamRate` | `Number:DataTransferRate` | x | Max. Upstream Rate |
|
||||
| `wanPhysicalLinkStatus` | `String` | x | Link Status |
|
||||
| `wanTotalBytesReceived` | `Number:DataAmount` | x | Total Bytes Received |
|
||||
| `wanTotalBytesSent` | `Number:DataAmount` | x | Total Bytes Send |
|
||||
| `wifi24GHzEnable` | `Switch` | | Enable/Disable the 2.4 GHz WiFi device. |
|
||||
| `wifi5GHzEnable` | `Switch` | | Enable/Disable the 5.0 GHz WiFi device. |
|
||||
| `wifiGuestEnable` | `Switch` | | Enable/Disable the guest WiFi. |
|
||||
|
||||
## `PHONEBOOK` Profile
|
||||
|
||||
The binding provides a profile for using the FritzBox phonebooks for resolving numbers to names.
|
||||
The `PHONEBOOK` profile takes strings containing the number as input and provides strings with the caller's name, if found.
|
||||
|
||||
The parameter `thingUid` with the UID of the phonebook providing thing is a mandatory parameter.
|
||||
If only a specific phonebook from the device should be used, this can be specified with the `phonebookName` parameter.
|
||||
The default is to use all available phonebooks from the specified thing.
|
||||
In case the format of the number in the phonebook and the format of the number from the channel are different (e.g. regarding country prefixes), the `matchCount` parameter can be used.
|
||||
The configured `matchCount` is counted from the right end and denotes the number of matching characters needed to consider this number as matching.
|
52
bundles/org.openhab.binding.tr064/pom.xml
Normal file
52
bundles/org.openhab.binding.tr064/pom.xml
Normal file
@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.tr064</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: TR-064 Binding</name>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jvnet.jaxb2.maven2</groupId>
|
||||
<artifactId>maven-jaxb2-plugin</artifactId>
|
||||
<version>0.14.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>generate-jaxb-sources</id>
|
||||
<goals>
|
||||
<goal>generate</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<schemaDirectory>src/main/resources/xsd</schemaDirectory>
|
||||
<noFileHeader>true</noFileHeader>
|
||||
<locale>en</locale>
|
||||
<episode>false</episode>
|
||||
<extension>true</extension>
|
||||
<args>
|
||||
<arg>-Xxew</arg>
|
||||
<arg>-Xxew:instantiate early</arg>
|
||||
</args>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.github.jaxb-xew-plugin</groupId>
|
||||
<artifactId>jaxb-xew-plugin</artifactId>
|
||||
<version>1.10</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.tr064-${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-tr064" description="TR-064 Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<requirement>openhab.tp;filter:="(feature=jaxb)"</requirement>
|
||||
<feature dependency="true">openhab.tp-jaxb</feature>
|
||||
<requirement>openhab.tp;filter:="(feature=jax-ws)"</requirement>
|
||||
<feature dependency="true">openhab.tp-jaxws</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.tr064/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -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.tr064.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.TrustAllTrustManager;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* Provides a TrustManager to allow secure connections to any FRITZ!Box
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial Contribution
|
||||
*/
|
||||
@Component
|
||||
@NonNullByDefault
|
||||
public class AvmFritzTlsTrustManagerProvider implements TlsTrustManagerProvider {
|
||||
|
||||
@Override
|
||||
public String getHostName() {
|
||||
return "fritz.box";
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509ExtendedTrustManager getTrustManager() {
|
||||
return TrustAllTrustManager.getInstance();
|
||||
}
|
||||
}
|
@ -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.tr064.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
*
|
||||
* The{@link ChannelConfigException} is a catched Exception that is thrown during channel configuration
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ChannelConfigException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ChannelConfigException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -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.tr064.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
*
|
||||
* The{@link PostProcessingException} is a catched Exception that is thrown in case of conversion errors during post
|
||||
* processing
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PostProcessingException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public PostProcessingException(String message, Throwable t) {
|
||||
super(message, t);
|
||||
}
|
||||
}
|
@ -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.tr064.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
*
|
||||
* The{@link SCPDException} is a catched Exception that is thrown in case of errors during SCPD processing
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SCPDException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public SCPDException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,254 @@
|
||||
/**
|
||||
* 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.tr064.internal;
|
||||
|
||||
import static org.openhab.binding.tr064.internal.util.Util.getSOAPElement;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.xml.soap.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.BytesContentProvider;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.tr064.internal.config.Tr064ChannelConfig;
|
||||
import org.openhab.binding.tr064.internal.dto.config.ActionType;
|
||||
import org.openhab.binding.tr064.internal.dto.config.ChannelTypeDescription;
|
||||
import org.openhab.binding.tr064.internal.dto.scpd.root.SCPDServiceType;
|
||||
import org.openhab.binding.tr064.internal.dto.scpd.service.SCPDActionType;
|
||||
import org.openhab.core.cache.ExpiringCacheMap;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link SOAPConnector} provides communication with a remote SOAP device
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SOAPConnector {
|
||||
private static final int SOAP_TIMEOUT = 2000; // in ms
|
||||
private final Logger logger = LoggerFactory.getLogger(SOAPConnector.class);
|
||||
private final HttpClient httpClient;
|
||||
private final String endpointBaseURL;
|
||||
private final SOAPValueConverter soapValueConverter;
|
||||
|
||||
public SOAPConnector(HttpClient httpClient, String endpointBaseURL) {
|
||||
this.httpClient = httpClient;
|
||||
this.endpointBaseURL = endpointBaseURL;
|
||||
this.soapValueConverter = new SOAPValueConverter(httpClient);
|
||||
}
|
||||
|
||||
/**
|
||||
* prepare a SOAP request for an action request to a service
|
||||
*
|
||||
* @param service the service
|
||||
* @param soapAction the action to send
|
||||
* @param arguments arguments to send along with the request
|
||||
* @return a jetty Request containing the full SOAP message
|
||||
* @throws IOException if a problem while writing the SOAP message to the Request occurs
|
||||
* @throws SOAPException if a problem with creating the SOAP message occurs
|
||||
*/
|
||||
private Request prepareSOAPRequest(SCPDServiceType service, String soapAction, Map<String, String> arguments)
|
||||
throws IOException, SOAPException {
|
||||
MessageFactory messageFactory = MessageFactory.newInstance();
|
||||
SOAPMessage soapMessage = messageFactory.createMessage();
|
||||
SOAPPart soapPart = soapMessage.getSOAPPart();
|
||||
SOAPEnvelope envelope = soapPart.getEnvelope();
|
||||
envelope.setEncodingStyle("http://schemas.xmlsoap.org/soap/encoding/");
|
||||
|
||||
// SOAP body
|
||||
SOAPBody soapBody = envelope.getBody();
|
||||
SOAPElement soapBodyElem = soapBody.addChildElement(soapAction, "u", service.getServiceType());
|
||||
arguments.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(argument -> {
|
||||
try {
|
||||
soapBodyElem.addChildElement(argument.getKey()).setTextContent(argument.getValue());
|
||||
} catch (SOAPException e) {
|
||||
logger.warn("Could not add {}:{} to SOAP Request: {}", argument.getKey(), argument.getValue(),
|
||||
e.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
// SOAP headers
|
||||
MimeHeaders headers = soapMessage.getMimeHeaders();
|
||||
headers.addHeader("SOAPAction", service.getServiceType() + "#" + soapAction);
|
||||
soapMessage.saveChanges();
|
||||
|
||||
// create Request and add headers and content
|
||||
Request request = httpClient.newRequest(endpointBaseURL + service.getControlURL()).method(HttpMethod.POST);
|
||||
((Iterator<MimeHeader>) soapMessage.getMimeHeaders().getAllHeaders())
|
||||
.forEachRemaining(header -> request.header(header.getName(), header.getValue()));
|
||||
try (final ByteArrayOutputStream os = new ByteArrayOutputStream()) {
|
||||
soapMessage.writeTo(os);
|
||||
byte[] content = os.toByteArray();
|
||||
request.content(new BytesContentProvider(content));
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* execute a SOAP request
|
||||
*
|
||||
* @param service the service to send the action to
|
||||
* @param soapAction the action itself
|
||||
* @param arguments arguments to send along with the request
|
||||
* @return the SOAPMessage answer from the remote host
|
||||
* @throws Tr064CommunicationException if an error occurs during the request
|
||||
*/
|
||||
public synchronized SOAPMessage doSOAPRequest(SCPDServiceType service, String soapAction,
|
||||
Map<String, String> arguments) throws Tr064CommunicationException {
|
||||
try {
|
||||
Request request = prepareSOAPRequest(service, soapAction, arguments).timeout(SOAP_TIMEOUT,
|
||||
TimeUnit.MILLISECONDS);
|
||||
if (logger.isTraceEnabled()) {
|
||||
request.getContent().forEach(buffer -> logger.trace("Request: {}", new String(buffer.array())));
|
||||
}
|
||||
|
||||
ContentResponse response = request.send();
|
||||
if (response.getStatus() == HttpStatus.UNAUTHORIZED_401) {
|
||||
// retry once if authentication expired
|
||||
logger.trace("Re-Auth needed.");
|
||||
httpClient.getAuthenticationStore().clearAuthenticationResults();
|
||||
request = prepareSOAPRequest(service, soapAction, arguments).timeout(SOAP_TIMEOUT,
|
||||
TimeUnit.MILLISECONDS);
|
||||
response = request.send();
|
||||
}
|
||||
try (final ByteArrayInputStream is = new ByteArrayInputStream(response.getContent())) {
|
||||
logger.trace("Received response: {}", response.getContentAsString());
|
||||
|
||||
SOAPMessage soapMessage = MessageFactory.newInstance().createMessage(null, is);
|
||||
if (soapMessage.getSOAPBody().hasFault()) {
|
||||
String soapError = getSOAPElement(soapMessage, "errorCode").orElse("unknown");
|
||||
String soapReason = getSOAPElement(soapMessage, "errorDescription").orElse("unknown");
|
||||
String error = String.format("HTTP-Response-Code %d (%s), SOAP-Fault: %s (%s)",
|
||||
response.getStatus(), response.getReason(), soapError, soapReason);
|
||||
throw new Tr064CommunicationException(error);
|
||||
}
|
||||
return soapMessage;
|
||||
}
|
||||
} catch (IOException | SOAPException | InterruptedException | TimeoutException | ExecutionException e) {
|
||||
throw new Tr064CommunicationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* send a command to the remote device
|
||||
*
|
||||
* @param channelConfig the channel config containing all information
|
||||
* @param command the command to send
|
||||
*/
|
||||
public void sendChannelCommandToDevice(Tr064ChannelConfig channelConfig, Command command) {
|
||||
soapValueConverter.getSOAPValueFromCommand(command, channelConfig.getDataType(),
|
||||
channelConfig.getChannelTypeDescription().getItem().getUnit()).ifPresentOrElse(value -> {
|
||||
final ChannelTypeDescription channelTypeDescription = channelConfig.getChannelTypeDescription();
|
||||
final SCPDServiceType service = channelConfig.getService();
|
||||
logger.debug("Sending {} as {} to {}/{}", command, value, service.getServiceId(),
|
||||
channelTypeDescription.getSetAction().getName());
|
||||
try {
|
||||
Map<String, String> arguments = new HashMap<>();
|
||||
if (channelTypeDescription.getSetAction().getArgument() != null) {
|
||||
arguments.put(channelTypeDescription.getSetAction().getArgument(), value);
|
||||
}
|
||||
String parameter = channelConfig.getParameter();
|
||||
if (parameter != null) {
|
||||
arguments.put(
|
||||
channelConfig.getChannelTypeDescription().getGetAction().getParameter().getName(),
|
||||
parameter);
|
||||
}
|
||||
doSOAPRequest(service, channelTypeDescription.getSetAction().getName(), arguments);
|
||||
} catch (Tr064CommunicationException e) {
|
||||
logger.warn("Could not send command {}: {}", command, e.getMessage());
|
||||
}
|
||||
}, () -> logger.warn("Could not convert {} to SOAP value", command));
|
||||
}
|
||||
|
||||
/**
|
||||
* get a value from the remote device - updates state cache for all possible channels
|
||||
*
|
||||
* @param channelConfig the channel config containing all information
|
||||
* @param channelConfigMap map of all channels in the device
|
||||
* @param stateCache the ExpiringCacheMap for states of the device
|
||||
* @return the value for the requested channel
|
||||
*/
|
||||
public State getChannelStateFromDevice(final Tr064ChannelConfig channelConfig,
|
||||
Map<ChannelUID, Tr064ChannelConfig> channelConfigMap, ExpiringCacheMap<ChannelUID, State> stateCache) {
|
||||
try {
|
||||
final SCPDActionType getAction = channelConfig.getGetAction();
|
||||
if (getAction == null) {
|
||||
// channel has no get action, return a default
|
||||
switch (channelConfig.getDataType()) {
|
||||
case "boolean":
|
||||
return OnOffType.OFF;
|
||||
case "string":
|
||||
return StringType.EMPTY;
|
||||
default:
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
|
||||
// get value(s) from remote device
|
||||
Map<String, String> arguments = new HashMap<>();
|
||||
String parameter = channelConfig.getParameter();
|
||||
ActionType action = channelConfig.getChannelTypeDescription().getGetAction();
|
||||
if (parameter != null && !action.getParameter().isInternalOnly()) {
|
||||
arguments.put(action.getParameter().getName(), parameter);
|
||||
}
|
||||
SOAPMessage soapResponse = doSOAPRequest(channelConfig.getService(), getAction.getName(), arguments);
|
||||
|
||||
String argumentName = channelConfig.getChannelTypeDescription().getGetAction().getArgument();
|
||||
// find all other channels with the same action that are already in cache, so we can update them
|
||||
Map<ChannelUID, Tr064ChannelConfig> channelsInRequest = channelConfigMap.entrySet().stream()
|
||||
.filter(map -> getAction.equals(map.getValue().getGetAction())
|
||||
&& stateCache.containsKey(map.getKey())
|
||||
&& !argumentName
|
||||
.equals(map.getValue().getChannelTypeDescription().getGetAction().getArgument()))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
channelsInRequest
|
||||
.forEach(
|
||||
(channelUID,
|
||||
channelConfig1) -> soapValueConverter
|
||||
.getStateFromSOAPValue(soapResponse,
|
||||
channelConfig1.getChannelTypeDescription().getGetAction()
|
||||
.getArgument(),
|
||||
channelConfig1)
|
||||
.ifPresent(state -> stateCache.putValue(channelUID, state)));
|
||||
|
||||
return soapValueConverter.getStateFromSOAPValue(soapResponse, argumentName, channelConfig)
|
||||
.orElseThrow(() -> new Tr064CommunicationException("failed to transform '"
|
||||
+ channelConfig.getChannelTypeDescription().getGetAction().getArgument() + "'"));
|
||||
} catch (Tr064CommunicationException e) {
|
||||
logger.info("Failed to get {}: {}", channelConfig, e.getMessage());
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,255 @@
|
||||
/**
|
||||
* 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.tr064.internal;
|
||||
|
||||
import static org.openhab.binding.tr064.internal.util.Util.getSOAPElement;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import javax.xml.soap.SOAPMessage;
|
||||
|
||||
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.openhab.binding.tr064.internal.config.Tr064ChannelConfig;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link SOAPValueConverter} converts SOAP values and openHAB states
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SOAPValueConverter {
|
||||
private static final int REQUEST_TIMEOUT = 5000; // in ms
|
||||
private final Logger logger = LoggerFactory.getLogger(SOAPValueConverter.class);
|
||||
private final HttpClient httpClient;
|
||||
|
||||
public SOAPValueConverter(HttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* convert an openHAB command to a SOAP value
|
||||
*
|
||||
* @param command the command to be converted
|
||||
* @param dataType the datatype to send
|
||||
* @param unit if available, the unit of the converted value
|
||||
* @return a string optional containing the converted value
|
||||
*/
|
||||
public Optional<String> getSOAPValueFromCommand(Command command, String dataType, String unit) {
|
||||
if (dataType.isEmpty()) {
|
||||
// we don't have data to send
|
||||
return Optional.of("");
|
||||
}
|
||||
if (command instanceof QuantityType) {
|
||||
QuantityType<?> value = (unit.isEmpty()) ? ((QuantityType<?>) command)
|
||||
: ((QuantityType<?>) command).toUnit(unit);
|
||||
if (value == null) {
|
||||
logger.warn("Could not convert {} to unit {}", command, unit);
|
||||
return Optional.empty();
|
||||
}
|
||||
switch (dataType) {
|
||||
case "ui2":
|
||||
return Optional.of(String.valueOf(value.shortValue()));
|
||||
case "ui4":
|
||||
return Optional.of(String.valueOf(value.intValue()));
|
||||
default:
|
||||
}
|
||||
} else if (command instanceof DecimalType) {
|
||||
BigDecimal value = ((DecimalType) command).toBigDecimal();
|
||||
switch (dataType) {
|
||||
case "ui2":
|
||||
return Optional.of(String.valueOf(value.shortValue()));
|
||||
case "ui4":
|
||||
return Optional.of(String.valueOf(value.intValue()));
|
||||
default:
|
||||
}
|
||||
} else if (command instanceof StringType) {
|
||||
if (dataType.equals("string")) {
|
||||
return Optional.of(command.toString());
|
||||
}
|
||||
} else if (command instanceof OnOffType) {
|
||||
if (dataType.equals("boolean")) {
|
||||
return Optional.of(OnOffType.ON.equals(command) ? "1" : "0");
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* convert the value from a SOAP message to an openHAB value
|
||||
*
|
||||
* @param soapMessage the inbound SOAP message
|
||||
* @param element the element that needs to be extracted
|
||||
* @param channelConfig the channel config containing additional information (if null a data-type "string" and
|
||||
* missing unit is assumed)
|
||||
* @return an Optional of State containing the converted value
|
||||
*/
|
||||
public Optional<State> getStateFromSOAPValue(SOAPMessage soapMessage, String element,
|
||||
@Nullable Tr064ChannelConfig channelConfig) {
|
||||
String dataType = channelConfig != null ? channelConfig.getDataType() : "string";
|
||||
String unit = channelConfig != null ? channelConfig.getChannelTypeDescription().getItem().getUnit() : "";
|
||||
|
||||
return getSOAPElement(soapMessage, element).map(rawValue -> {
|
||||
// map rawValue to State
|
||||
switch (dataType) {
|
||||
case "boolean":
|
||||
return rawValue.equals("0") ? OnOffType.OFF : OnOffType.ON;
|
||||
case "string":
|
||||
return new StringType(rawValue);
|
||||
case "ui2":
|
||||
case "ui4":
|
||||
if (!unit.isEmpty()) {
|
||||
return new QuantityType<>(rawValue + " " + unit);
|
||||
} else {
|
||||
return new DecimalType(rawValue);
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}).map(state -> {
|
||||
// check if we need post processing
|
||||
if (channelConfig == null
|
||||
|| channelConfig.getChannelTypeDescription().getGetAction().getPostProcessor() == null) {
|
||||
return state;
|
||||
}
|
||||
String postProcessor = channelConfig.getChannelTypeDescription().getGetAction().getPostProcessor();
|
||||
try {
|
||||
Method method = SOAPValueConverter.class.getDeclaredMethod(postProcessor, State.class,
|
||||
Tr064ChannelConfig.class);
|
||||
Object o = method.invoke(this, state, channelConfig);
|
||||
if (o instanceof State) {
|
||||
return (State) o;
|
||||
}
|
||||
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||
logger.warn("Postprocessor {} not found, this most likely is a programming error", postProcessor, e);
|
||||
} catch (InvocationTargetException e) {
|
||||
Throwable cause = e.getCause();
|
||||
logger.info("Postprocessor {} failed: {}", postProcessor,
|
||||
cause != null ? cause.getMessage() : e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}).or(Optional::empty);
|
||||
}
|
||||
|
||||
/**
|
||||
* post processor for answering machine new messages channel
|
||||
*
|
||||
* @param state the message list URL
|
||||
* @param channelConfig channel config of the TAM new message channel
|
||||
* @return the number of new messages
|
||||
* @throws PostProcessingException if the message list could not be retrieved
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private State processTamListURL(State state, Tr064ChannelConfig channelConfig) throws PostProcessingException {
|
||||
try {
|
||||
ContentResponse response = httpClient.newRequest(state.toString()).timeout(1000, TimeUnit.MILLISECONDS)
|
||||
.send();
|
||||
String responseContent = response.getContentAsString();
|
||||
int messageCount = responseContent.split("<New>1</New>").length - 1;
|
||||
|
||||
return new DecimalType(messageCount);
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
throw new PostProcessingException("Failed to get TAM list from URL " + state.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* post processor for missed calls
|
||||
*
|
||||
* @param state the call list URL
|
||||
* @param channelConfig channel config of the missed call channel (contains day number)
|
||||
* @return the number of missed calls
|
||||
* @throws PostProcessingException if call list could not be retrieved
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private State processMissedCalls(State state, Tr064ChannelConfig channelConfig) throws PostProcessingException {
|
||||
return processCallList(state, channelConfig.getParameter(), "2");
|
||||
}
|
||||
|
||||
/**
|
||||
* post processor for inbound calls
|
||||
*
|
||||
* @param state the call list URL
|
||||
* @param channelConfig channel config of the inbound call channel (contains day number)
|
||||
* @return the number of inbound calls
|
||||
* @throws PostProcessingException if call list could not be retrieved
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private State processInboundCalls(State state, Tr064ChannelConfig channelConfig) throws PostProcessingException {
|
||||
return processCallList(state, channelConfig.getParameter(), "1");
|
||||
}
|
||||
|
||||
/**
|
||||
* post processor for rejected calls
|
||||
*
|
||||
* @param state the call list URL
|
||||
* @param channelConfig channel config of the rejected call channel (contains day number)
|
||||
* @return the number of rejected calls
|
||||
* @throws PostProcessingException if call list could not be retrieved
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private State processRejectedCalls(State state, Tr064ChannelConfig channelConfig) throws PostProcessingException {
|
||||
return processCallList(state, channelConfig.getParameter(), "3");
|
||||
}
|
||||
|
||||
/**
|
||||
* post processor for outbound calls
|
||||
*
|
||||
* @param state the call list URL
|
||||
* @param channelConfig channel config of the outbound call channel (contains day number)
|
||||
* @return the number of outbound calls
|
||||
* @throws PostProcessingException if call list could not be retrieved
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private State processOutboundCalls(State state, Tr064ChannelConfig channelConfig) throws PostProcessingException {
|
||||
return processCallList(state, channelConfig.getParameter(), "4");
|
||||
}
|
||||
|
||||
/**
|
||||
* internal helper for call list post processors
|
||||
*
|
||||
* @param state the call list URL
|
||||
* @param days number of days to get
|
||||
* @param type type of call (1=missed 2=inbound 3=rejected 4=outbund)
|
||||
* @return the quantity of calls of the given type within the given number of days
|
||||
* @throws PostProcessingException if the call list could not be retrieved
|
||||
*/
|
||||
private State processCallList(State state, @Nullable String days, String type) throws PostProcessingException {
|
||||
try {
|
||||
ContentResponse response = httpClient.newRequest(state.toString() + "&days=" + days)
|
||||
.timeout(REQUEST_TIMEOUT, TimeUnit.MILLISECONDS).send();
|
||||
String responseContent = response.getContentAsString();
|
||||
int callCount = responseContent.split("<Type>" + type + "</Type>").length - 1;
|
||||
|
||||
return new DecimalType(callCount);
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
throw new PostProcessingException("Failed to get call list from URL " + state.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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.tr064.internal;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.tr064.internal.dto.config.ChannelTypeDescription;
|
||||
import org.openhab.binding.tr064.internal.util.Util;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link Tr064BindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Tr064BindingConstants {
|
||||
public static final String BINDING_ID = "tr064";
|
||||
|
||||
public static final ThingTypeUID THING_TYPE_GENERIC = new ThingTypeUID(BINDING_ID, "generic");
|
||||
public static final ThingTypeUID THING_TYPE_FRITZBOX = new ThingTypeUID(BINDING_ID, "fritzbox");
|
||||
public static final ThingTypeUID THING_TYPE_SUBDEVICE = new ThingTypeUID(BINDING_ID, "subdevice");
|
||||
public static final ThingTypeUID THING_TYPE_SUBDEVICE_LAN = new ThingTypeUID(BINDING_ID, "subdeviceLan");
|
||||
|
||||
public static final List<ChannelTypeDescription> CHANNEL_TYPES = Util.readXMLChannelConfig();
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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.tr064.internal;
|
||||
|
||||
import static org.openhab.binding.tr064.internal.Tr064BindingConstants.CHANNEL_TYPES;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.type.*;
|
||||
import org.openhab.core.types.StateDescriptionFragmentBuilder;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* The {@link Tr064ChannelTypeProvider} is used for providing dynamic channel types
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { ChannelTypeProvider.class, Tr064ChannelTypeProvider.class })
|
||||
public class Tr064ChannelTypeProvider implements ChannelTypeProvider {
|
||||
private final Map<ChannelTypeUID, ChannelType> channelTypeMap = new ConcurrentHashMap<>();
|
||||
|
||||
public Tr064ChannelTypeProvider() {
|
||||
CHANNEL_TYPES.forEach(channelTypeDescription -> {
|
||||
ChannelTypeUID channelTypeUID = new ChannelTypeUID(Tr064BindingConstants.BINDING_ID,
|
||||
channelTypeDescription.getName());
|
||||
// create state description
|
||||
StateDescriptionFragmentBuilder stateDescriptionFragmentBuilder = StateDescriptionFragmentBuilder.create()
|
||||
.withReadOnly(channelTypeDescription.getSetAction() == null);
|
||||
if (channelTypeDescription.getItem().getStatePattern() != null) {
|
||||
stateDescriptionFragmentBuilder.withPattern(channelTypeDescription.getItem().getStatePattern());
|
||||
}
|
||||
|
||||
// create channel type
|
||||
ChannelTypeBuilder<StateChannelTypeBuilder> channelTypeBuilder = ChannelTypeBuilder
|
||||
.state(channelTypeUID, channelTypeDescription.getLabel(),
|
||||
channelTypeDescription.getItem().getType())
|
||||
.withStateDescriptionFragment(stateDescriptionFragmentBuilder.build())
|
||||
.isAdvanced(channelTypeDescription.isAdvanced());
|
||||
if (channelTypeDescription.getDescription() != null) {
|
||||
channelTypeBuilder.withDescription(channelTypeDescription.getDescription());
|
||||
}
|
||||
|
||||
channelTypeMap.put(channelTypeUID, channelTypeBuilder.build());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ChannelType> getChannelTypes(@Nullable Locale locale) {
|
||||
return channelTypeMap.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ChannelType getChannelType(ChannelTypeUID channelTypeUID, @Nullable Locale locale) {
|
||||
return channelTypeMap.get(channelTypeUID);
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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.tr064.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link Tr064CommunicationException} is thrown for communication errors
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Tr064CommunicationException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public Tr064CommunicationException(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
public Tr064CommunicationException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
/**
|
||||
* 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.tr064.internal;
|
||||
|
||||
import static org.openhab.binding.tr064.internal.Tr064BindingConstants.THING_TYPE_SUBDEVICE;
|
||||
import static org.openhab.binding.tr064.internal.Tr064BindingConstants.THING_TYPE_SUBDEVICE_LAN;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.tr064.internal.dto.scpd.root.SCPDDeviceType;
|
||||
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.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.BridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.util.UIDUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link Tr064DiscoveryService} discovers sub devices of a root device.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Tr064DiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
|
||||
private static final int SEARCH_TIME = 5;
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_SUBDEVICE);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(Tr064DiscoveryService.class);
|
||||
private @Nullable Tr064RootHandler bridgeHandler;
|
||||
|
||||
public Tr064DiscoveryService() {
|
||||
super(SEARCH_TIME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof Tr064RootHandler) {
|
||||
this.bridgeHandler = (Tr064RootHandler) thingHandler;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return bridgeHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
BridgeHandler bridgeHandler = this.bridgeHandler;
|
||||
if (bridgeHandler == null) {
|
||||
logger.warn("Bridgehandler not found, could not cleanup discovery results.");
|
||||
return;
|
||||
}
|
||||
removeOlderResults(new Date().getTime(), bridgeHandler.getThing().getUID());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypes() {
|
||||
return SUPPORTED_THING_TYPES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startScan() {
|
||||
Tr064RootHandler bridgeHandler = this.bridgeHandler;
|
||||
if (bridgeHandler == null) {
|
||||
logger.warn("Could not start discovery, bridge handler not set");
|
||||
return;
|
||||
}
|
||||
List<SCPDDeviceType> devices = bridgeHandler.getAllSubDevices();
|
||||
ThingUID bridgeUID = bridgeHandler.getThing().getUID();
|
||||
devices.forEach(device -> {
|
||||
logger.trace("Trying to add {} to discovery results on {}", device, bridgeUID);
|
||||
String udn = device.getUDN();
|
||||
if (udn != null) {
|
||||
ThingTypeUID thingTypeUID;
|
||||
if ("urn:dslforum-org:device:LANDevice:1".equals(device.getDeviceType())) {
|
||||
thingTypeUID = THING_TYPE_SUBDEVICE_LAN;
|
||||
} else {
|
||||
thingTypeUID = THING_TYPE_SUBDEVICE;
|
||||
}
|
||||
ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, UIDUtils.encode(udn));
|
||||
|
||||
Map<String, Object> properties = new HashMap<>(2);
|
||||
properties.put("uuid", udn);
|
||||
properties.put("deviceType", device.getDeviceType());
|
||||
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(thingUID).withLabel(device.getFriendlyName())
|
||||
.withBridge(bridgeHandler.getThing().getUID()).withProperties(properties)
|
||||
.withRepresentationProperty("uuid").build();
|
||||
thingDiscovered(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void stopScan() {
|
||||
super.stopScan();
|
||||
removeOlderResults(getTimestampOfLastScan());
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 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.tr064.internal;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
|
||||
import org.openhab.core.types.StateDescription;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Dynamic channel state description provider.
|
||||
* Overrides the state description for the controls, which receive its configuration in the runtime.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { DynamicStateDescriptionProvider.class, Tr064DynamicStateDescriptionProvider.class })
|
||||
public class Tr064DynamicStateDescriptionProvider implements DynamicStateDescriptionProvider {
|
||||
private final Logger logger = LoggerFactory.getLogger(Tr064DynamicStateDescriptionProvider.class);
|
||||
private final Map<ChannelUID, StateDescription> descriptions = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Set a state description for a channel. This description will be used when preparing the channel state by
|
||||
* the framework for presentation. A previous description, if existed, will be replaced.
|
||||
*
|
||||
* @param channelUID channel UID
|
||||
* @param description state description for the channel
|
||||
*/
|
||||
public void setDescription(ChannelUID channelUID, StateDescription description) {
|
||||
logger.trace("adding state description for channel {}", channelUID);
|
||||
descriptions.put(channelUID, description);
|
||||
}
|
||||
|
||||
/**
|
||||
* remove all descriptions for a given thing
|
||||
*
|
||||
* @param thingUID the thing's UID
|
||||
*/
|
||||
public void removeDescriptionsForThing(ThingUID thingUID) {
|
||||
logger.trace("removing state description for thing {}", thingUID);
|
||||
descriptions.entrySet().removeIf(entry -> entry.getKey().getThingUID().equals(thingUID));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable StateDescription getStateDescription(Channel channel,
|
||||
@Nullable StateDescription originalStateDescription, @Nullable Locale locale) {
|
||||
if (descriptions.containsKey(channel.getUID())) {
|
||||
return descriptions.get(channel.getUID());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
/**
|
||||
* 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.tr064.internal;
|
||||
|
||||
import static org.openhab.binding.tr064.internal.Tr064BindingConstants.THING_TYPE_FRITZBOX;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.tr064.internal.phonebook.PhonebookProfileFactory;
|
||||
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;
|
||||
|
||||
/**
|
||||
* The {@link Tr064HandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { ThingHandlerFactory.class }, configurationPid = "binding.tr064")
|
||||
public class Tr064HandlerFactory extends BaseThingHandlerFactory {
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
|
||||
.of(Tr064RootHandler.SUPPORTED_THING_TYPES, Tr064SubHandler.SUPPORTED_THING_TYPES).flatMap(Set::stream)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private final PhonebookProfileFactory phonebookProfileFactory;
|
||||
|
||||
// the Tr064ChannelTypeProvider is needed for creating the channels and
|
||||
// referenced here to make sure it is available before things are
|
||||
// initialized
|
||||
@SuppressWarnings("unused")
|
||||
private final Tr064ChannelTypeProvider channelTypeProvider;
|
||||
|
||||
@Activate
|
||||
public Tr064HandlerFactory(@Reference HttpClientFactory httpClientFactory,
|
||||
@Reference Tr064ChannelTypeProvider channelTypeProvider,
|
||||
@Reference PhonebookProfileFactory phonebookProfileFactory) {
|
||||
httpClient = httpClientFactory.getCommonHttpClient();
|
||||
this.channelTypeProvider = channelTypeProvider;
|
||||
this.phonebookProfileFactory = phonebookProfileFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (Tr064RootHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||
Tr064RootHandler handler = new Tr064RootHandler((Bridge) thing, httpClient);
|
||||
if (thingTypeUID.equals(THING_TYPE_FRITZBOX)) {
|
||||
phonebookProfileFactory.registerPhonebookProvider(handler);
|
||||
}
|
||||
return handler;
|
||||
} else if (Tr064SubHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||
return new Tr064SubHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeHandler(ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof Tr064RootHandler) {
|
||||
phonebookProfileFactory.unregisterPhonebookProvider((Tr064RootHandler) thingHandler);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,386 @@
|
||||
/**
|
||||
* 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.tr064.internal;
|
||||
|
||||
import static org.openhab.binding.tr064.internal.Tr064BindingConstants.THING_TYPE_FRITZBOX;
|
||||
import static org.openhab.binding.tr064.internal.Tr064BindingConstants.THING_TYPE_GENERIC;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.xml.soap.SOAPException;
|
||||
import javax.xml.soap.SOAPMessage;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.Authentication;
|
||||
import org.eclipse.jetty.client.api.AuthenticationStore;
|
||||
import org.eclipse.jetty.client.util.DigestAuthentication;
|
||||
import org.openhab.binding.tr064.internal.config.Tr064ChannelConfig;
|
||||
import org.openhab.binding.tr064.internal.config.Tr064RootConfiguration;
|
||||
import org.openhab.binding.tr064.internal.dto.scpd.root.SCPDDeviceType;
|
||||
import org.openhab.binding.tr064.internal.dto.scpd.root.SCPDServiceType;
|
||||
import org.openhab.binding.tr064.internal.dto.scpd.service.SCPDActionType;
|
||||
import org.openhab.binding.tr064.internal.phonebook.Phonebook;
|
||||
import org.openhab.binding.tr064.internal.phonebook.PhonebookProvider;
|
||||
import org.openhab.binding.tr064.internal.phonebook.Tr064PhonebookImpl;
|
||||
import org.openhab.binding.tr064.internal.util.SCPDUtil;
|
||||
import org.openhab.binding.tr064.internal.util.Util;
|
||||
import org.openhab.core.cache.ExpiringCacheMap;
|
||||
import org.openhab.core.thing.*;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link Tr064RootHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels and update channel values
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Tr064RootHandler extends BaseBridgeHandler implements PhonebookProvider {
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_GENERIC, THING_TYPE_FRITZBOX);
|
||||
private static final int RETRY_INTERVAL = 60;
|
||||
private static final Set<String> PROPERTY_ARGUMENTS = Set.of("NewSerialNumber", "NewSoftwareVersion",
|
||||
"NewModelName");
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(Tr064RootHandler.class);
|
||||
private final HttpClient httpClient;
|
||||
|
||||
private Tr064RootConfiguration config = new Tr064RootConfiguration();
|
||||
private String deviceType = "";
|
||||
|
||||
private @Nullable SCPDUtil scpdUtil;
|
||||
private SOAPConnector soapConnector;
|
||||
private String endpointBaseURL = "http://fritz.box:49000";
|
||||
|
||||
private final Map<ChannelUID, Tr064ChannelConfig> channels = new HashMap<>();
|
||||
// caching is used to prevent excessive calls to the same action
|
||||
private final ExpiringCacheMap<ChannelUID, State> stateCache = new ExpiringCacheMap<>(2000);
|
||||
private Collection<Phonebook> phonebooks = Collections.emptyList();
|
||||
|
||||
private @Nullable ScheduledFuture<?> connectFuture;
|
||||
private @Nullable ScheduledFuture<?> pollFuture;
|
||||
private @Nullable ScheduledFuture<?> phonebookFuture;
|
||||
|
||||
Tr064RootHandler(Bridge bridge, HttpClient httpClient) {
|
||||
super(bridge);
|
||||
this.httpClient = httpClient;
|
||||
soapConnector = new SOAPConnector(httpClient, endpointBaseURL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
Tr064ChannelConfig channelConfig = channels.get(channelUID);
|
||||
if (channelConfig == null) {
|
||||
logger.trace("Channel {} not supported.", channelUID);
|
||||
return;
|
||||
}
|
||||
|
||||
if (command instanceof RefreshType) {
|
||||
SOAPConnector soapConnector = this.soapConnector;
|
||||
State state = stateCache.putIfAbsentAndGet(channelUID,
|
||||
() -> soapConnector.getChannelStateFromDevice(channelConfig, channels, stateCache));
|
||||
if (state != null) {
|
||||
updateState(channelUID, state);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (channelConfig.getChannelTypeDescription().getSetAction() == null) {
|
||||
logger.debug("Discarding command {} to {}, read-only channel", command, channelUID);
|
||||
return;
|
||||
}
|
||||
scheduler.execute(() -> soapConnector.sendChannelCommandToDevice(channelConfig, command));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(Tr064RootConfiguration.class);
|
||||
if (!config.isValid()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"At least one mandatory configuration field is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
endpointBaseURL = "http://" + config.host + ":49000";
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
connectFuture = scheduler.scheduleWithFixedDelay(this::internalInitialize, 0, RETRY_INTERVAL, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* internal thing initializer (sets SCPDUtil and connects to remote device)
|
||||
*/
|
||||
private void internalInitialize() {
|
||||
try {
|
||||
scpdUtil = new SCPDUtil(httpClient, endpointBaseURL);
|
||||
} catch (SCPDException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"could not get device definitions from " + config.host);
|
||||
return;
|
||||
}
|
||||
|
||||
if (establishSecureConnectionAndUpdateProperties()) {
|
||||
removeConnectScheduler();
|
||||
|
||||
// connection successful, check channels
|
||||
ThingBuilder thingBuilder = editThing();
|
||||
thingBuilder.withoutChannels(thing.getChannels());
|
||||
final SCPDUtil scpdUtil = this.scpdUtil;
|
||||
if (scpdUtil != null) {
|
||||
Util.checkAvailableChannels(thing, thingBuilder, scpdUtil, "", deviceType, channels);
|
||||
updateThing(thingBuilder.build());
|
||||
}
|
||||
|
||||
installPolling();
|
||||
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeConnectScheduler() {
|
||||
final ScheduledFuture<?> connectFuture = this.connectFuture;
|
||||
if (connectFuture != null) {
|
||||
connectFuture.cancel(true);
|
||||
this.connectFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
removeConnectScheduler();
|
||||
uninstallPolling();
|
||||
stateCache.clear();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* poll remote device for channel values
|
||||
*/
|
||||
private void poll() {
|
||||
channels.forEach((channelUID, channelConfig) -> {
|
||||
if (isLinked(channelUID)) {
|
||||
State state = stateCache.putIfAbsentAndGet(channelUID,
|
||||
() -> soapConnector.getChannelStateFromDevice(channelConfig, channels, stateCache));
|
||||
if (state != null) {
|
||||
updateState(channelUID, state);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* establish the connection - get secure port (if avallable), install authentication, get device properties
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
private boolean establishSecureConnectionAndUpdateProperties() {
|
||||
final SCPDUtil scpdUtil = this.scpdUtil;
|
||||
if (scpdUtil != null) {
|
||||
try {
|
||||
SCPDDeviceType device = scpdUtil.getDevice("")
|
||||
.orElseThrow(() -> new SCPDException("Root device not found"));
|
||||
SCPDServiceType deviceService = device.getServiceList().stream()
|
||||
.filter(service -> service.getServiceId().equals("urn:DeviceInfo-com:serviceId:DeviceInfo1"))
|
||||
.findFirst().orElseThrow(() -> new SCPDException(
|
||||
"service 'urn:DeviceInfo-com:serviceId:DeviceInfo1' not found"));
|
||||
|
||||
this.deviceType = device.getDeviceType();
|
||||
|
||||
// try to get security (https) port
|
||||
SOAPMessage soapResponse = soapConnector.doSOAPRequest(deviceService, "GetSecurityPort",
|
||||
Collections.emptyMap());
|
||||
if (!soapResponse.getSOAPBody().hasFault()) {
|
||||
SOAPValueConverter soapValueConverter = new SOAPValueConverter(httpClient);
|
||||
soapValueConverter.getStateFromSOAPValue(soapResponse, "NewSecurityPort", null)
|
||||
.ifPresentOrElse(port -> {
|
||||
endpointBaseURL = "https://" + config.host + ":" + port.toString();
|
||||
soapConnector = new SOAPConnector(httpClient, endpointBaseURL);
|
||||
logger.debug("endpointBaseURL is now '{}'", endpointBaseURL);
|
||||
}, () -> logger.warn("Could not determine secure port, disabling https"));
|
||||
} else {
|
||||
logger.warn("Could not determine secure port, disabling https");
|
||||
}
|
||||
|
||||
// clear auth cache and force re-auth
|
||||
httpClient.getAuthenticationStore().clearAuthenticationResults();
|
||||
AuthenticationStore auth = httpClient.getAuthenticationStore();
|
||||
auth.addAuthentication(new DigestAuthentication(new URI(endpointBaseURL), Authentication.ANY_REALM,
|
||||
config.user, config.password));
|
||||
|
||||
// check & update properties
|
||||
SCPDActionType getInfoAction = scpdUtil.getService(deviceService.getServiceId())
|
||||
.orElseThrow(() -> new SCPDException(
|
||||
"Could not get service definition for 'urn:DeviceInfo-com:serviceId:DeviceInfo1'"))
|
||||
.getActionList().stream().filter(action -> action.getName().equals("GetInfo")).findFirst()
|
||||
.orElseThrow(() -> new SCPDException("Action 'GetInfo' not found"));
|
||||
SOAPMessage soapResponse1 = soapConnector.doSOAPRequest(deviceService, getInfoAction.getName(),
|
||||
Collections.emptyMap());
|
||||
SOAPValueConverter soapValueConverter = new SOAPValueConverter(httpClient);
|
||||
Map<String, String> properties = editProperties();
|
||||
PROPERTY_ARGUMENTS.forEach(argumentName -> getInfoAction.getArgumentList().stream()
|
||||
.filter(argument -> argument.getName().equals(argumentName)).findFirst()
|
||||
.ifPresent(argument -> soapValueConverter
|
||||
.getStateFromSOAPValue(soapResponse1, argumentName, null).ifPresent(value -> properties
|
||||
.put(argument.getRelatedStateVariable(), value.toString()))));
|
||||
properties.put("deviceType", device.getDeviceType());
|
||||
updateProperties(properties);
|
||||
|
||||
return true;
|
||||
} catch (SCPDException | SOAPException | Tr064CommunicationException | URISyntaxException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* get all sub devices of this root device (used for discovery)
|
||||
*
|
||||
* @return the list
|
||||
*/
|
||||
public List<SCPDDeviceType> getAllSubDevices() {
|
||||
final SCPDUtil scpdUtil = this.scpdUtil;
|
||||
return (scpdUtil == null) ? Collections.emptyList() : scpdUtil.getAllSubDevices();
|
||||
}
|
||||
|
||||
/**
|
||||
* get the SOAP connector (used by sub devices for communication with the remote device)
|
||||
*
|
||||
* @return the SOAP connector
|
||||
*/
|
||||
public SOAPConnector getSOAPConnector() {
|
||||
return soapConnector;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the SCPD processing utility
|
||||
*
|
||||
* @return the SCPD utility (or null if not available)
|
||||
*/
|
||||
public @Nullable SCPDUtil getSCPDUtil() {
|
||||
return scpdUtil;
|
||||
}
|
||||
|
||||
/**
|
||||
* uninstall the polling
|
||||
*/
|
||||
private void uninstallPolling() {
|
||||
final ScheduledFuture<?> pollFuture = this.pollFuture;
|
||||
if (pollFuture != null) {
|
||||
pollFuture.cancel(true);
|
||||
this.pollFuture = null;
|
||||
}
|
||||
final ScheduledFuture<?> phonebookFuture = this.phonebookFuture;
|
||||
if (phonebookFuture != null) {
|
||||
phonebookFuture.cancel(true);
|
||||
this.phonebookFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* install the polling
|
||||
*/
|
||||
private void installPolling() {
|
||||
uninstallPolling();
|
||||
pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 0, config.refresh, TimeUnit.SECONDS);
|
||||
if (config.phonebookInterval > 0) {
|
||||
phonebookFuture = scheduler.scheduleWithFixedDelay(this::retrievePhonebooks, 0, config.phonebookInterval,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Collection<Phonebook> processPhonebookList(SOAPMessage soapMessagePhonebookList,
|
||||
SCPDServiceType scpdService) {
|
||||
SOAPValueConverter soapValueConverter = new SOAPValueConverter(httpClient);
|
||||
return (Collection<Phonebook>) soapValueConverter
|
||||
.getStateFromSOAPValue(soapMessagePhonebookList, "NewPhonebookList", null)
|
||||
.map(phonebookList -> Arrays.stream(phonebookList.toString().split(","))).orElse(Stream.empty())
|
||||
.map(index -> {
|
||||
try {
|
||||
SOAPMessage soapMessageURL = soapConnector.doSOAPRequest(scpdService, "GetPhonebook",
|
||||
Map.of("NewPhonebookID", index));
|
||||
return soapValueConverter.getStateFromSOAPValue(soapMessageURL, "NewPhonebookURL", null)
|
||||
.map(url -> (Phonebook) new Tr064PhonebookImpl(httpClient, url.toString()));
|
||||
} catch (Tr064CommunicationException e) {
|
||||
logger.warn("Failed to get phonebook with index {}:", index, e);
|
||||
}
|
||||
return Optional.empty();
|
||||
}).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void retrievePhonebooks() {
|
||||
String serviceId = "urn:X_AVM-DE_OnTel-com:serviceId:X_AVM-DE_OnTel1";
|
||||
SCPDUtil scpdUtil = this.scpdUtil;
|
||||
if (scpdUtil == null) {
|
||||
logger.warn("Cannot find SCPDUtil. This is most likely a programming error.");
|
||||
return;
|
||||
}
|
||||
Optional<SCPDServiceType> scpdService = scpdUtil.getDevice("").flatMap(deviceType -> deviceType.getServiceList()
|
||||
.stream().filter(service -> service.getServiceId().equals(serviceId)).findFirst());
|
||||
|
||||
phonebooks = scpdService.map(service -> {
|
||||
try {
|
||||
return processPhonebookList(
|
||||
soapConnector.doSOAPRequest(service, "GetPhonebookList", Collections.emptyMap()), service);
|
||||
} catch (Tr064CommunicationException e) {
|
||||
return Collections.<Phonebook> emptyList();
|
||||
}
|
||||
}).orElse(Collections.emptyList());
|
||||
|
||||
if (phonebooks.isEmpty()) {
|
||||
logger.warn("Could not get phonebooks for thing {}", thing.getUID());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Phonebook> getPhonebookByName(String name) {
|
||||
return phonebooks.stream().filter(p -> name.equals(p.getName())).findAny();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Phonebook> getPhonebooks() {
|
||||
return phonebooks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThingUID getUID() {
|
||||
return thing.getUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFriendlyName() {
|
||||
String friendlyName = thing.getLabel();
|
||||
return friendlyName != null ? friendlyName : getUID().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Set.of(Tr064DiscoveryService.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,254 @@
|
||||
/**
|
||||
* 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.tr064.internal;
|
||||
|
||||
import static org.openhab.binding.tr064.internal.Tr064BindingConstants.THING_TYPE_SUBDEVICE;
|
||||
import static org.openhab.binding.tr064.internal.Tr064BindingConstants.THING_TYPE_SUBDEVICE_LAN;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
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.tr064.internal.config.Tr064ChannelConfig;
|
||||
import org.openhab.binding.tr064.internal.config.Tr064SubConfiguration;
|
||||
import org.openhab.binding.tr064.internal.dto.scpd.root.SCPDDeviceType;
|
||||
import org.openhab.binding.tr064.internal.util.SCPDUtil;
|
||||
import org.openhab.binding.tr064.internal.util.Util;
|
||||
import org.openhab.core.cache.ExpiringCacheMap;
|
||||
import org.openhab.core.thing.*;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
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;
|
||||
|
||||
/**
|
||||
* The {@link Tr064SubHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Tr064SubHandler extends BaseThingHandler {
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_SUBDEVICE,
|
||||
THING_TYPE_SUBDEVICE_LAN);
|
||||
private static final int RETRY_INTERVAL = 60;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(Tr064SubHandler.class);
|
||||
|
||||
private Tr064SubConfiguration config = new Tr064SubConfiguration();
|
||||
|
||||
private String deviceType = "";
|
||||
private boolean isInitialized = false;
|
||||
|
||||
private final Map<ChannelUID, Tr064ChannelConfig> channels = new HashMap<>();
|
||||
// caching is used to prevent excessive calls to the same action
|
||||
private final ExpiringCacheMap<ChannelUID, State> stateCache = new ExpiringCacheMap<>(2000);
|
||||
|
||||
private @Nullable SOAPConnector soapConnector;
|
||||
private @Nullable ScheduledFuture<?> connectFuture;
|
||||
private @Nullable ScheduledFuture<?> pollFuture;
|
||||
|
||||
Tr064SubHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("null")
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
Tr064ChannelConfig channelConfig = channels.get(channelUID);
|
||||
if (channelConfig == null) {
|
||||
logger.trace("Channel {} not supported.", channelUID);
|
||||
return;
|
||||
}
|
||||
|
||||
if (command instanceof RefreshType) {
|
||||
State state = stateCache.putIfAbsentAndGet(channelUID, () -> soapConnector == null ? UnDefType.UNDEF
|
||||
: soapConnector.getChannelStateFromDevice(channelConfig, channels, stateCache));
|
||||
if (state != null) {
|
||||
updateState(channelUID, state);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (channelConfig.getChannelTypeDescription().getSetAction() == null) {
|
||||
logger.debug("Discarding command {} to {}, read-only channel", command, channelUID);
|
||||
return;
|
||||
}
|
||||
scheduler.execute(() -> {
|
||||
if (soapConnector == null) {
|
||||
logger.warn("Could not send command because connector not available");
|
||||
} else {
|
||||
soapConnector.sendChannelCommandToDevice(channelConfig, command);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(Tr064SubConfiguration.class);
|
||||
if (!config.isValid()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"One or more mandatory configuration fields are empty");
|
||||
return;
|
||||
}
|
||||
|
||||
final Bridge bridge = getBridge();
|
||||
if (bridge != null && bridge.getStatus().equals(ThingStatus.ONLINE)) {
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
connectFuture = scheduler.scheduleWithFixedDelay(this::internalInitialize, 0, 30, TimeUnit.SECONDS);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
private void internalInitialize() {
|
||||
final Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
return;
|
||||
}
|
||||
final Tr064RootHandler bridgeHandler = (Tr064RootHandler) bridge.getHandler();
|
||||
if (bridgeHandler == null) {
|
||||
logger.warn("Bridge-handler is null in thing {}", thing.getUID());
|
||||
return;
|
||||
}
|
||||
final SCPDUtil scpdUtil = bridgeHandler.getSCPDUtil();
|
||||
if (scpdUtil == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Could not get device definitions");
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkProperties(scpdUtil)) {
|
||||
// properties set, check channels
|
||||
ThingBuilder thingBuilder = editThing();
|
||||
thingBuilder.withoutChannels(thing.getChannels());
|
||||
Util.checkAvailableChannels(thing, thingBuilder, scpdUtil, config.uuid, deviceType, channels);
|
||||
updateThing(thingBuilder.build());
|
||||
|
||||
// remove connect scheduler
|
||||
removeConnectScheduler();
|
||||
soapConnector = bridgeHandler.getSOAPConnector();
|
||||
|
||||
isInitialized = true;
|
||||
installPolling();
|
||||
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeConnectScheduler() {
|
||||
final ScheduledFuture<?> connectFuture = this.connectFuture;
|
||||
if (connectFuture != null) {
|
||||
connectFuture.cancel(true);
|
||||
this.connectFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
removeConnectScheduler();
|
||||
uninstallPolling();
|
||||
|
||||
stateCache.clear();
|
||||
isInitialized = false;
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* poll remote device for channel values
|
||||
*/
|
||||
private void poll() {
|
||||
SOAPConnector soapConnector = this.soapConnector;
|
||||
channels.forEach((channelUID, channelConfig) -> {
|
||||
if (isLinked(channelUID)) {
|
||||
State state = stateCache.putIfAbsentAndGet(channelUID, () -> soapConnector == null ? UnDefType.UNDEF
|
||||
: soapConnector.getChannelStateFromDevice(channelConfig, channels, stateCache));
|
||||
if (state != null) {
|
||||
updateState(channelUID, state);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* get device properties from remote device
|
||||
*
|
||||
* @param scpdUtil the SCPD util of this device
|
||||
* @return true if successfull
|
||||
*/
|
||||
private boolean checkProperties(SCPDUtil scpdUtil) {
|
||||
try {
|
||||
SCPDDeviceType device = scpdUtil.getDevice(config.uuid)
|
||||
.orElseThrow(() -> new SCPDException("Could not find device " + config.uuid));
|
||||
String deviceType = device.getDeviceType();
|
||||
if (deviceType == null) {
|
||||
throw new SCPDException("deviceType can't be null ");
|
||||
}
|
||||
this.deviceType = deviceType;
|
||||
|
||||
Map<String, String> properties = editProperties();
|
||||
properties.put("deviceType", deviceType);
|
||||
updateProperties(properties);
|
||||
|
||||
return true;
|
||||
} catch (SCPDException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Failed to update device properties: " + e.getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
if (!bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
removeConnectScheduler();
|
||||
} else {
|
||||
if (isInitialized) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
connectFuture = scheduler.scheduleWithFixedDelay(this::internalInitialize, 0, RETRY_INTERVAL,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* uninstall update polling
|
||||
*/
|
||||
private void uninstallPolling() {
|
||||
final ScheduledFuture<?> pollFuture = this.pollFuture;
|
||||
if (pollFuture != null) {
|
||||
pollFuture.cancel(true);
|
||||
this.pollFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* install update polling
|
||||
*/
|
||||
private void installPolling() {
|
||||
uninstallPolling();
|
||||
pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 0, config.refresh, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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.tr064.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link Tr064BaseThingConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Tr064BaseThingConfiguration {
|
||||
public int refresh = 60;
|
||||
}
|
@ -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.tr064.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.tr064.internal.dto.config.ChannelTypeDescription;
|
||||
import org.openhab.binding.tr064.internal.dto.scpd.root.SCPDServiceType;
|
||||
import org.openhab.binding.tr064.internal.dto.scpd.service.SCPDActionType;
|
||||
|
||||
/**
|
||||
* The {@link Tr064ChannelConfig} class holds a channel configuration
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Tr064ChannelConfig {
|
||||
private ChannelTypeDescription channelTypeDescription;
|
||||
private SCPDServiceType service;
|
||||
private @Nullable SCPDActionType getAction;
|
||||
private String dataType = "";
|
||||
private @Nullable String parameter;
|
||||
|
||||
public Tr064ChannelConfig(ChannelTypeDescription channelTypeDescription, SCPDServiceType service) {
|
||||
this.channelTypeDescription = channelTypeDescription;
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
public Tr064ChannelConfig(Tr064ChannelConfig o) {
|
||||
this.channelTypeDescription = o.channelTypeDescription;
|
||||
this.service = o.service;
|
||||
this.getAction = o.getAction;
|
||||
this.dataType = o.dataType;
|
||||
this.parameter = o.parameter;
|
||||
}
|
||||
|
||||
public ChannelTypeDescription getChannelTypeDescription() {
|
||||
return channelTypeDescription;
|
||||
}
|
||||
|
||||
public SCPDServiceType getService() {
|
||||
return service;
|
||||
}
|
||||
|
||||
public String getDataType() {
|
||||
return dataType;
|
||||
}
|
||||
|
||||
public void setDataType(String dataType) {
|
||||
this.dataType = dataType;
|
||||
}
|
||||
|
||||
public @Nullable SCPDActionType getGetAction() {
|
||||
return getAction;
|
||||
}
|
||||
|
||||
public void setGetAction(SCPDActionType getAction) {
|
||||
this.getAction = getAction;
|
||||
}
|
||||
|
||||
public @Nullable String getParameter() {
|
||||
return parameter;
|
||||
}
|
||||
|
||||
public void setParameter(String parameter) {
|
||||
this.parameter = parameter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final SCPDActionType getAction = this.getAction;
|
||||
return "Tr064ChannelConfig{" + "channelType=" + channelTypeDescription.getName() + ", getAction="
|
||||
+ ((getAction == null) ? "(null)" : getAction.getName()) + ", dataType='" + dataType + ", parameter='"
|
||||
+ parameter + "'}";
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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.tr064.internal.config;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link Tr064RootConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Tr064RootConfiguration extends Tr064BaseThingConfiguration {
|
||||
public String host = "";
|
||||
public String user = "dslf-config";
|
||||
public String password = "";
|
||||
|
||||
/* following parameters only available in fritzbox thing */
|
||||
public List<String> tamIndices = Collections.emptyList();
|
||||
public List<String> callDeflectionIndices = Collections.emptyList();
|
||||
public List<String> missedCallDays = Collections.emptyList();
|
||||
public List<String> rejectedCallDays = Collections.emptyList();
|
||||
public List<String> inboundCallDays = Collections.emptyList();
|
||||
public List<String> outboundCallDays = Collections.emptyList();
|
||||
public int phonebookInterval = 0;
|
||||
|
||||
public boolean isValid() {
|
||||
return !host.isEmpty() && !user.isEmpty() && !password.isEmpty();
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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.tr064.internal.config;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link Tr064SubConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Tr064SubConfiguration extends Tr064BaseThingConfiguration {
|
||||
public String uuid = "";
|
||||
|
||||
// Lan Device
|
||||
public List<String> macOnline = Collections.emptyList();
|
||||
|
||||
public boolean isValid() {
|
||||
return !uuid.isEmpty();
|
||||
}
|
||||
}
|
@ -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.tr064.internal.phonebook;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link Phonebook} interface is used by phonebook providers to implement phonebooks
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface Phonebook {
|
||||
|
||||
/**
|
||||
* get the name of this phonebook
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* lookup a number in this phonebook
|
||||
*
|
||||
* @param number the number
|
||||
* @param matchCount the number of matching digits, counting from far right
|
||||
* @return an Optional containing the name associated with this number (empty of not present)
|
||||
*/
|
||||
Optional<String> lookupNumber(String number, int matchCount);
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
/**
|
||||
* 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.tr064.internal.phonebook;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.profiles.*;
|
||||
import org.openhab.core.transform.TransformationService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.util.UIDUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link PhonebookProfile} class provides a profile for resolving phone number strings to names
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PhonebookProfile implements StateProfile {
|
||||
public static final ProfileTypeUID PHONEBOOK_PROFILE_TYPE_UID = new ProfileTypeUID(
|
||||
TransformationService.TRANSFORM_PROFILE_SCOPE, "PHONEBOOK");
|
||||
public static final ProfileType PHONEBOOK_PROFILE_TYPE = ProfileTypeBuilder
|
||||
.newState(PhonebookProfile.PHONEBOOK_PROFILE_TYPE_UID, "Phonebook").build();
|
||||
|
||||
public static final String PHONEBOOK_PARAM = "phonebook";
|
||||
private static final String MATCH_COUNT_PARAM = "matchCount";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(PhonebookProfile.class);
|
||||
|
||||
private final ProfileCallback callback;
|
||||
|
||||
private final @Nullable String phonebookName;
|
||||
private final @Nullable ThingUID thingUID;
|
||||
private final Map<ThingUID, PhonebookProvider> phonebookProviders;
|
||||
private final int matchCount;
|
||||
|
||||
public PhonebookProfile(ProfileCallback callback, ProfileContext context,
|
||||
Map<ThingUID, PhonebookProvider> phonebookProviders) {
|
||||
this.callback = callback;
|
||||
this.phonebookProviders = phonebookProviders;
|
||||
|
||||
Configuration configuration = context.getConfiguration();
|
||||
Object phonebookParam = configuration.get(PHONEBOOK_PARAM);
|
||||
Object matchCountParam = configuration.get(MATCH_COUNT_PARAM);
|
||||
|
||||
logger.debug("Profile configured with '{}'='{}', '{}'='{}'", PHONEBOOK_PARAM, phonebookParam, MATCH_COUNT_PARAM,
|
||||
matchCountParam);
|
||||
|
||||
ThingUID thingUID;
|
||||
String phonebookName = null;
|
||||
int matchCount = 0;
|
||||
|
||||
try {
|
||||
if (!(phonebookParam instanceof String)
|
||||
|| ((matchCountParam != null) && !(matchCountParam instanceof String))) {
|
||||
throw new IllegalArgumentException("Parameters need to be Strings");
|
||||
}
|
||||
String[] phonebookParams = ((String) phonebookParam).split(":");
|
||||
if (phonebookParams.length > 2) {
|
||||
throw new IllegalArgumentException("Could not split 'phonebook' parameter");
|
||||
}
|
||||
thingUID = new ThingUID(UIDUtils.decode(phonebookParams[0]));
|
||||
if (phonebookParams.length == 2) {
|
||||
phonebookName = UIDUtils.decode(phonebookParams[1]);
|
||||
}
|
||||
if (matchCountParam != null) {
|
||||
matchCount = Integer.parseInt((String) matchCountParam);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.warn("Could not initialize PHONEBOOK transformation profile: {}. Profile will be inactive.",
|
||||
e.getMessage());
|
||||
thingUID = null;
|
||||
}
|
||||
|
||||
this.thingUID = thingUID;
|
||||
this.phonebookName = phonebookName;
|
||||
this.matchCount = matchCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommandFromItem(Command command) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommandFromHandler(Command command) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateUpdateFromHandler(State state) {
|
||||
if (state instanceof StringType) {
|
||||
PhonebookProvider provider = phonebookProviders.get(thingUID);
|
||||
if (provider == null) {
|
||||
logger.warn("Could not get phonebook provider with thing UID '{}'.", thingUID);
|
||||
return;
|
||||
}
|
||||
final String phonebookName = this.phonebookName;
|
||||
Optional<String> match;
|
||||
if (phonebookName != null) {
|
||||
match = provider.getPhonebookByName(phonebookName).or(() -> {
|
||||
logger.warn("Could not get phonebook '{}' from provider '{}'", phonebookName, thingUID);
|
||||
return Optional.empty();
|
||||
}).flatMap(phonebook -> phonebook.lookupNumber(state.toString(), matchCount));
|
||||
} else {
|
||||
match = provider.getPhonebooks().stream().map(p -> p.lookupNumber(state.toString(), matchCount))
|
||||
.filter(Optional::isPresent).map(Optional::get).findAny();
|
||||
}
|
||||
State newState = match.map(name -> (State) new StringType(name)).orElse(state);
|
||||
if (newState == state) {
|
||||
logger.debug("Number '{}' not found in phonebook '{}' from provider '{}'", state, phonebookName,
|
||||
thingUID);
|
||||
}
|
||||
callback.sendUpdate(newState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProfileTypeUID getProfileTypeUID() {
|
||||
return PHONEBOOK_PROFILE_TYPE_UID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateUpdateFromItem(State state) {
|
||||
}
|
||||
}
|
@ -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.tr064.internal.phonebook;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
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.core.config.core.ConfigOptionProvider;
|
||||
import org.openhab.core.config.core.ParameterOption;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.profiles.Profile;
|
||||
import org.openhab.core.thing.profiles.ProfileCallback;
|
||||
import org.openhab.core.thing.profiles.ProfileContext;
|
||||
import org.openhab.core.thing.profiles.ProfileFactory;
|
||||
import org.openhab.core.thing.profiles.ProfileType;
|
||||
import org.openhab.core.thing.profiles.ProfileTypeProvider;
|
||||
import org.openhab.core.thing.profiles.ProfileTypeUID;
|
||||
import org.openhab.core.util.UIDUtils;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link PhonebookProfileFactory} class is used to create phonebook profiles
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { ProfileFactory.class, ProfileTypeProvider.class, PhonebookProfileFactory.class,
|
||||
ConfigOptionProvider.class })
|
||||
public class PhonebookProfileFactory implements ProfileFactory, ProfileTypeProvider, ConfigOptionProvider {
|
||||
private final Logger logger = LoggerFactory.getLogger(PhonebookProfileFactory.class);
|
||||
private final Map<ThingUID, PhonebookProvider> phonebookProviders = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public @Nullable Profile createProfile(ProfileTypeUID profileTypeUID, ProfileCallback callback,
|
||||
ProfileContext profileContext) {
|
||||
return new PhonebookProfile(callback, profileContext, phonebookProviders);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ProfileTypeUID> getSupportedProfileTypeUIDs() {
|
||||
return Collections.singleton(PhonebookProfile.PHONEBOOK_PROFILE_TYPE_UID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ProfileType> getProfileTypes(@Nullable Locale locale) {
|
||||
return Collections.singleton(PhonebookProfile.PHONEBOOK_PROFILE_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* register a phonebook provider
|
||||
*
|
||||
* @param phonebookProvider the provider that shall be added
|
||||
*/
|
||||
public void registerPhonebookProvider(PhonebookProvider phonebookProvider) {
|
||||
if (phonebookProviders.put(phonebookProvider.getUID(), phonebookProvider) != null) {
|
||||
logger.warn("Tried to register a phonebook provider with UID '{}' for the second time.",
|
||||
phonebookProvider.getUID());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* unregister a phonebook provider
|
||||
*
|
||||
* @param phonebookProvider the provider that shall be removed
|
||||
*/
|
||||
public void unregisterPhonebookProvider(PhonebookProvider phonebookProvider) {
|
||||
if (phonebookProviders.remove(phonebookProvider.getUID()) == null) {
|
||||
logger.warn("Tried to unregister a phonebook provider with UID '{}' but it was not found.",
|
||||
phonebookProvider.getUID());
|
||||
}
|
||||
}
|
||||
|
||||
private Stream<ParameterOption> createPhonebookList(Map.Entry<ThingUID, PhonebookProvider> entry) {
|
||||
String thingUid = UIDUtils.encode(entry.getKey().toString());
|
||||
String thingName = entry.getValue().getFriendlyName();
|
||||
|
||||
Stream<ParameterOption> parameterOptions = entry.getValue().getPhonebooks().stream()
|
||||
.map(phonebook -> new ParameterOption(thingUid + ":" + UIDUtils.encode(phonebook.getName()),
|
||||
thingName + " " + phonebook.getName()));
|
||||
|
||||
if (parameterOptions.count() > 0) {
|
||||
return Stream.concat(Stream.of(new ParameterOption(thingUid, thingName)), parameterOptions);
|
||||
}
|
||||
|
||||
return parameterOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Collection<ParameterOption> getParameterOptions(URI uri, String s, @Nullable String s1,
|
||||
@Nullable Locale locale) {
|
||||
if (uri.getSchemeSpecificPart().equals(PhonebookProfile.PHONEBOOK_PROFILE_TYPE_UID.toString())
|
||||
&& s.equals(PhonebookProfile.PHONEBOOK_PARAM)) {
|
||||
return phonebookProviders.entrySet().stream().flatMap(this::createPhonebookList)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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.tr064.internal.phonebook;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
|
||||
/**
|
||||
* The {@link PhonebookProvider} interface provides methods to lookup a phone number from a phonebook
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface PhonebookProvider {
|
||||
|
||||
Optional<Phonebook> getPhonebookByName(String name);
|
||||
|
||||
Collection<Phonebook> getPhonebooks();
|
||||
|
||||
ThingUID getUID();
|
||||
|
||||
String getFriendlyName();
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
/**
|
||||
* 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.tr064.internal.phonebook;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.tr064.internal.dto.phonebook.NumberType;
|
||||
import org.openhab.binding.tr064.internal.dto.phonebook.PhonebooksType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link Tr064PhonebookImpl} class implements a phonebook
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Tr064PhonebookImpl implements Phonebook {
|
||||
private final Logger logger = LoggerFactory.getLogger(Tr064PhonebookImpl.class);
|
||||
|
||||
private Map<String, String> phonebook = new HashMap<>();
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private final String phonebookUrl;
|
||||
|
||||
private String phonebookName = "";
|
||||
|
||||
public Tr064PhonebookImpl(HttpClient httpClient, String phonebookUrl) {
|
||||
this.httpClient = httpClient;
|
||||
this.phonebookUrl = phonebookUrl;
|
||||
getPhonebook();
|
||||
}
|
||||
|
||||
private void getPhonebook() {
|
||||
try {
|
||||
ContentResponse contentResponse = httpClient.newRequest(phonebookUrl).method(HttpMethod.GET)
|
||||
.timeout(2, TimeUnit.SECONDS).send();
|
||||
InputStream xml = new ByteArrayInputStream(contentResponse.getContent());
|
||||
|
||||
JAXBContext context = JAXBContext.newInstance(PhonebooksType.class);
|
||||
Unmarshaller um = context.createUnmarshaller();
|
||||
PhonebooksType phonebooksType = um.unmarshal(new StreamSource(xml), PhonebooksType.class).getValue();
|
||||
|
||||
phonebookName = phonebooksType.getPhonebook().getName();
|
||||
|
||||
phonebook = phonebooksType.getPhonebook().getContact().stream().map(contact -> {
|
||||
String contactName = contact.getPerson().getRealName();
|
||||
return contact.getTelephony().getNumber().stream()
|
||||
.collect(Collectors.toMap(NumberType::getValue, number -> contactName));
|
||||
}).collect(HashMap::new, HashMap::putAll, HashMap::putAll);
|
||||
logger.debug("Downloaded phonebook {}: {}", phonebookName, phonebook);
|
||||
} catch (JAXBException | InterruptedException | ExecutionException | TimeoutException e) {
|
||||
logger.warn("Failed to get phonebook with URL {}:", phonebookUrl, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return phonebookName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> lookupNumber(String number, int matchCount) {
|
||||
String matchString = matchCount < number.length() ? number.substring(number.length() - matchCount) : number;
|
||||
logger.trace("matchString for {} is {}", number, matchString);
|
||||
return phonebook.keySet().stream().filter(n -> n.endsWith(matchString)).findAny().map(phonebook::get);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Phonebook{" + "phonebookName='" + phonebookName + "', phonebook=" + phonebook + '}';
|
||||
}
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
/**
|
||||
* 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.tr064.internal.util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
|
||||
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.http.HttpMethod;
|
||||
import org.openhab.binding.tr064.internal.SCPDException;
|
||||
import org.openhab.binding.tr064.internal.dto.scpd.root.SCPDDeviceType;
|
||||
import org.openhab.binding.tr064.internal.dto.scpd.root.SCPDRootType;
|
||||
import org.openhab.binding.tr064.internal.dto.scpd.root.SCPDServiceType;
|
||||
import org.openhab.binding.tr064.internal.dto.scpd.service.SCPDScpdType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link SCPDUtil} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SCPDUtil {
|
||||
private final Logger logger = LoggerFactory.getLogger(SCPDUtil.class);
|
||||
|
||||
private final HttpClient httpClient;
|
||||
|
||||
private SCPDRootType scpdRoot;
|
||||
private final List<SCPDDeviceType> scpdDevicesList = new ArrayList<>();
|
||||
private final Map<String, SCPDScpdType> serviceMap = new HashMap<>();
|
||||
|
||||
public SCPDUtil(HttpClient httpClient, String endpoint) throws SCPDException {
|
||||
this.httpClient = httpClient;
|
||||
|
||||
SCPDRootType scpdRoot = getAndUnmarshalSCPD(endpoint + "/tr64desc.xml", SCPDRootType.class);
|
||||
if (scpdRoot == null) {
|
||||
throw new SCPDException("could not get SCPD root");
|
||||
}
|
||||
this.scpdRoot = scpdRoot;
|
||||
|
||||
scpdDevicesList.addAll(flatDeviceList(scpdRoot.getDevice()).collect(Collectors.toList()));
|
||||
for (SCPDDeviceType device : scpdDevicesList) {
|
||||
for (SCPDServiceType service : device.getServiceList()) {
|
||||
SCPDScpdType scpd = serviceMap.computeIfAbsent(service.getServiceId(),
|
||||
serviceId -> getAndUnmarshalSCPD(endpoint + service.getSCPDURL(), SCPDScpdType.class));
|
||||
if (scpd == null) {
|
||||
throw new SCPDException("could not get SCPD service");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* generic unmarshaller
|
||||
*
|
||||
* @param uri the uri of the XML file
|
||||
* @param clazz the class describing the XML file
|
||||
* @return unmarshalling result
|
||||
*/
|
||||
private <T> @Nullable T getAndUnmarshalSCPD(String uri, Class<T> clazz) {
|
||||
try {
|
||||
ContentResponse contentResponse = httpClient.newRequest(uri).timeout(2, TimeUnit.SECONDS)
|
||||
.method(HttpMethod.GET).send();
|
||||
InputStream xml = new ByteArrayInputStream(contentResponse.getContent());
|
||||
|
||||
JAXBContext context = JAXBContext.newInstance(clazz);
|
||||
Unmarshaller um = context.createUnmarshaller();
|
||||
return um.unmarshal(new StreamSource(xml), clazz).getValue();
|
||||
} catch (ExecutionException | InterruptedException | TimeoutException e) {
|
||||
logger.debug("HTTP Failed to GET uri '{}': {}", uri, e.getMessage());
|
||||
} catch (JAXBException e) {
|
||||
logger.debug("Unmarshalling failed: {}", e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* recursively flatten the device tree to a stream
|
||||
*
|
||||
* @param device a device
|
||||
* @return stream of sub-devices
|
||||
*/
|
||||
private Stream<SCPDDeviceType> flatDeviceList(SCPDDeviceType device) {
|
||||
return Stream.concat(Stream.of(device), device.getDeviceList().stream().flatMap(this::flatDeviceList));
|
||||
}
|
||||
|
||||
/**
|
||||
* get a list of all sub-devices (root device not included)
|
||||
*
|
||||
* @return the device list
|
||||
*/
|
||||
public List<SCPDDeviceType> getAllSubDevices() {
|
||||
return scpdDevicesList.stream().filter(device -> !device.getUDN().equals(scpdRoot.getDevice().getUDN()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* get a single device by it's UDN
|
||||
*
|
||||
* @param udn the device UDN
|
||||
* @return the device
|
||||
*/
|
||||
public Optional<SCPDDeviceType> getDevice(String udn) {
|
||||
if (udn.isEmpty()) {
|
||||
return Optional.of(scpdRoot.getDevice());
|
||||
} else {
|
||||
return getAllSubDevices().stream().filter(device -> udn.equals(device.getUDN())).findFirst();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get a single service by it's serviceId
|
||||
*
|
||||
* @param serviceId the service id
|
||||
* @return the service
|
||||
*/
|
||||
public Optional<SCPDScpdType> getService(String serviceId) {
|
||||
return Optional.ofNullable(serviceMap.get(serviceId));
|
||||
}
|
||||
}
|
@ -0,0 +1,293 @@
|
||||
/**
|
||||
* 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.tr064.internal.util;
|
||||
|
||||
import static org.openhab.binding.tr064.internal.Tr064BindingConstants.*;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBElement;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
import javax.xml.soap.SOAPException;
|
||||
import javax.xml.soap.SOAPMessage;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.tr064.internal.ChannelConfigException;
|
||||
import org.openhab.binding.tr064.internal.Tr064RootHandler;
|
||||
import org.openhab.binding.tr064.internal.config.Tr064BaseThingConfiguration;
|
||||
import org.openhab.binding.tr064.internal.config.Tr064ChannelConfig;
|
||||
import org.openhab.binding.tr064.internal.config.Tr064RootConfiguration;
|
||||
import org.openhab.binding.tr064.internal.config.Tr064SubConfiguration;
|
||||
import org.openhab.binding.tr064.internal.dto.config.ActionType;
|
||||
import org.openhab.binding.tr064.internal.dto.config.ChannelTypeDescription;
|
||||
import org.openhab.binding.tr064.internal.dto.config.ChannelTypeDescriptions;
|
||||
import org.openhab.binding.tr064.internal.dto.config.ParameterType;
|
||||
import org.openhab.binding.tr064.internal.dto.scpd.root.SCPDServiceType;
|
||||
import org.openhab.binding.tr064.internal.dto.scpd.service.*;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.util.UIDUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
/**
|
||||
* The {@link Util} is a set of helper functions
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Util {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Util.class);
|
||||
|
||||
/**
|
||||
* read the channel config from the resource file (static initialization)
|
||||
*
|
||||
* @return a list of all available channel configurations
|
||||
*/
|
||||
public static List<ChannelTypeDescription> readXMLChannelConfig() {
|
||||
try {
|
||||
InputStream resource = Thread.currentThread().getContextClassLoader().getResourceAsStream("channels.xml");
|
||||
JAXBContext context = JAXBContext.newInstance(ChannelTypeDescriptions.class);
|
||||
Unmarshaller um = context.createUnmarshaller();
|
||||
JAXBElement<ChannelTypeDescriptions> root = um.unmarshal(new StreamSource(resource),
|
||||
ChannelTypeDescriptions.class);
|
||||
return root.getValue().getChannel();
|
||||
} catch (JAXBException e) {
|
||||
LOGGER.warn("Failed to read channel definitions", e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract an argument from an SCPD action definition
|
||||
*
|
||||
* @param scpdAction the action object
|
||||
* @param argumentName the argument's name
|
||||
* @param direction the direction (in or out)
|
||||
* @return the requested argument object
|
||||
* @throws ChannelConfigException if not found
|
||||
*/
|
||||
private static SCPDArgumentType getArgument(SCPDActionType scpdAction, String argumentName, SCPDDirection direction)
|
||||
throws ChannelConfigException {
|
||||
return scpdAction.getArgumentList().stream()
|
||||
.filter(argument -> argument.getName().equals(argumentName) && argument.getDirection() == direction)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new ChannelConfigException(
|
||||
(direction == SCPDDirection.IN ? "Set-Argument '" : "Get-Argument '") + argumentName
|
||||
+ "' not found"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the related state variable from the service root for a given argument
|
||||
*
|
||||
* @param serviceRoot the service root object
|
||||
* @param scpdArgument the argument object
|
||||
* @return the related state variable object for this argument
|
||||
* @throws ChannelConfigException if not found
|
||||
*/
|
||||
private static SCPDStateVariableType getStateVariable(SCPDScpdType serviceRoot, SCPDArgumentType scpdArgument)
|
||||
throws ChannelConfigException {
|
||||
return serviceRoot.getServiceStateTable().stream()
|
||||
.filter(stateVariable -> stateVariable.getName().equals(scpdArgument.getRelatedStateVariable()))
|
||||
.findFirst().orElseThrow(() -> new ChannelConfigException(
|
||||
"StateVariable '" + scpdArgument.getRelatedStateVariable() + "' not found"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract an action from the service root
|
||||
*
|
||||
* @param serviceRoot the service root object
|
||||
* @param actionName the action name
|
||||
* @param actionType "Get-Action" or "Set-Action" (for exception string only)
|
||||
* @return the requested action object
|
||||
* @throws ChannelConfigException if not found
|
||||
*/
|
||||
private static SCPDActionType getAction(SCPDScpdType serviceRoot, String actionName, String actionType)
|
||||
throws ChannelConfigException {
|
||||
return serviceRoot.getActionList().stream().filter(action -> actionName.equals(action.getName())).findFirst()
|
||||
.orElseThrow(() -> new ChannelConfigException(actionType + " '" + actionName + "' not found"));
|
||||
}
|
||||
|
||||
/**
|
||||
* check and add available channels on a thing
|
||||
*
|
||||
* @param thing the Thing
|
||||
* @param thingBuilder the ThingBuilder (needs to be passed as editThing is only available in the handler)
|
||||
* @param scpdUtil the SCPDUtil instance for this thing
|
||||
* @param deviceId the device id for this thing
|
||||
* @param deviceType the (SCPD) device-type for this thing
|
||||
* @param channels a (mutable) channel list for storing all channels
|
||||
*/
|
||||
public static void checkAvailableChannels(Thing thing, ThingBuilder thingBuilder, SCPDUtil scpdUtil,
|
||||
String deviceId, String deviceType, Map<ChannelUID, Tr064ChannelConfig> channels) {
|
||||
Tr064BaseThingConfiguration thingConfig = Tr064RootHandler.SUPPORTED_THING_TYPES
|
||||
.contains(thing.getThingTypeUID()) ? thing.getConfiguration().as(Tr064RootConfiguration.class)
|
||||
: thing.getConfiguration().as(Tr064SubConfiguration.class);
|
||||
channels.clear();
|
||||
CHANNEL_TYPES.stream().filter(channel -> deviceType.equals(channel.getService().getDeviceType()))
|
||||
.forEach(channelTypeDescription -> {
|
||||
String channelId = channelTypeDescription.getName();
|
||||
String serviceId = channelTypeDescription.getService().getServiceId();
|
||||
Set<String> parameters = new HashSet<>();
|
||||
try {
|
||||
SCPDServiceType deviceService = scpdUtil.getDevice(deviceId)
|
||||
.flatMap(device -> device.getServiceList().stream()
|
||||
.filter(service -> service.getServiceId().equals(serviceId)).findFirst())
|
||||
.orElseThrow(() -> new ChannelConfigException("Service '" + serviceId + "' not found"));
|
||||
SCPDScpdType serviceRoot = scpdUtil.getService(deviceService.getServiceId())
|
||||
.orElseThrow(() -> new ChannelConfigException(
|
||||
"Service definition for '" + serviceId + "' not found"));
|
||||
Tr064ChannelConfig channelConfig = new Tr064ChannelConfig(channelTypeDescription,
|
||||
deviceService);
|
||||
|
||||
// get
|
||||
ActionType getAction = channelTypeDescription.getGetAction();
|
||||
if (getAction != null) {
|
||||
String actionName = getAction.getName();
|
||||
String argumentName = getAction.getArgument();
|
||||
SCPDActionType scpdAction = getAction(serviceRoot, actionName, "Get-Action");
|
||||
SCPDArgumentType scpdArgument = getArgument(scpdAction, argumentName, SCPDDirection.OUT);
|
||||
SCPDStateVariableType relatedStateVariable = getStateVariable(serviceRoot, scpdArgument);
|
||||
parameters.addAll(
|
||||
getAndCheckParameters(channelId, getAction, scpdAction, serviceRoot, thingConfig));
|
||||
|
||||
channelConfig.setGetAction(scpdAction);
|
||||
channelConfig.setDataType(relatedStateVariable.getDataType());
|
||||
}
|
||||
|
||||
// check set action
|
||||
ActionType setAction = channelTypeDescription.getSetAction();
|
||||
if (setAction != null) {
|
||||
String actionName = setAction.getName();
|
||||
String argumentName = setAction.getArgument();
|
||||
|
||||
SCPDActionType scpdAction = getAction(serviceRoot, actionName, "Set-Action");
|
||||
if (argumentName != null) {
|
||||
SCPDArgumentType scpdArgument = getArgument(scpdAction, argumentName, SCPDDirection.IN);
|
||||
SCPDStateVariableType relatedStateVariable = getStateVariable(serviceRoot,
|
||||
scpdArgument);
|
||||
if (channelConfig.getDataType().isEmpty()) {
|
||||
channelConfig.setDataType(relatedStateVariable.getDataType());
|
||||
} else if (!channelConfig.getDataType().equals(relatedStateVariable.getDataType())) {
|
||||
throw new ChannelConfigException("dataType of set and get action are different");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// everything is available, create the channel
|
||||
ChannelTypeUID channelTypeUID = new ChannelTypeUID(BINDING_ID,
|
||||
channelTypeDescription.getName());
|
||||
if (parameters.isEmpty()) {
|
||||
// we have no parameters, so create a single channel
|
||||
ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
|
||||
ChannelBuilder channelBuilder = ChannelBuilder
|
||||
.create(channelUID, channelTypeDescription.getItem().getType())
|
||||
.withType(channelTypeUID);
|
||||
thingBuilder.withChannel(channelBuilder.build());
|
||||
channels.put(channelUID, channelConfig);
|
||||
} else {
|
||||
// create a channel for each parameter
|
||||
parameters.forEach(parameter -> {
|
||||
String normalizedParameter = UIDUtils.encode(parameter);
|
||||
ChannelUID channelUID = new ChannelUID(thing.getUID(),
|
||||
channelId + "_" + normalizedParameter);
|
||||
ChannelBuilder channelBuilder = ChannelBuilder
|
||||
.create(channelUID, channelTypeDescription.getItem().getType())
|
||||
.withType(channelTypeUID)
|
||||
.withLabel(channelTypeDescription.getLabel() + " " + parameter);
|
||||
thingBuilder.withChannel(channelBuilder.build());
|
||||
Tr064ChannelConfig channelConfig1 = new Tr064ChannelConfig(channelConfig);
|
||||
channelConfig1.setParameter(parameter);
|
||||
channels.put(channelUID, channelConfig1);
|
||||
});
|
||||
}
|
||||
} catch (ChannelConfigException e) {
|
||||
LOGGER.debug("Channel {} not available: {}", channelId, e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static Set<String> getAndCheckParameters(String channelId, ActionType action, SCPDActionType scpdAction,
|
||||
SCPDScpdType serviceRoot, Tr064BaseThingConfiguration thingConfig) throws ChannelConfigException {
|
||||
ParameterType parameter = action.getParameter();
|
||||
if (parameter == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
try {
|
||||
Set<String> parameters = new HashSet<>();
|
||||
|
||||
// get parameters by reflection from thing config
|
||||
Field paramField = thingConfig.getClass().getField(parameter.getThingParameter());
|
||||
Object rawFieldValue = paramField.get(thingConfig);
|
||||
if ((rawFieldValue instanceof List<?>)) {
|
||||
((List<?>) rawFieldValue).forEach(obj -> {
|
||||
if (obj instanceof String) {
|
||||
parameters.add((String) obj);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// validate parameter against pattern
|
||||
String parameterPattern = parameter.getPattern();
|
||||
if (parameterPattern != null) {
|
||||
parameters.removeIf(param -> !param.matches(parameterPattern));
|
||||
}
|
||||
|
||||
// validate parameter against SCPD (if not internal only)
|
||||
if (!parameter.isInternalOnly()) {
|
||||
SCPDArgumentType scpdArgument = getArgument(scpdAction, parameter.getName(), SCPDDirection.IN);
|
||||
SCPDStateVariableType relatedStateVariable = getStateVariable(serviceRoot, scpdArgument);
|
||||
if (relatedStateVariable.getAllowedValueRange() != null) {
|
||||
int paramMin = relatedStateVariable.getAllowedValueRange().getMinimum();
|
||||
int paramMax = relatedStateVariable.getAllowedValueRange().getMaximum();
|
||||
int paramStep = relatedStateVariable.getAllowedValueRange().getStep();
|
||||
Set<String> allowedValues = Stream.iterate(paramMin, i -> i <= paramMax, i -> i + paramStep)
|
||||
.map(String::valueOf).collect(Collectors.toSet());
|
||||
parameters.retainAll(allowedValues);
|
||||
}
|
||||
}
|
||||
|
||||
// check we have at least one valid parameter left
|
||||
if (parameters.isEmpty()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
return parameters;
|
||||
} catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException e) {
|
||||
throw new ChannelConfigException("Could not get required parameter '" + channelId
|
||||
+ "' from thing config (missing, empty or invalid)");
|
||||
}
|
||||
}
|
||||
|
||||
public static Optional<String> getSOAPElement(SOAPMessage soapMessage, String elementName) {
|
||||
try {
|
||||
NodeList nodeList = soapMessage.getSOAPBody().getElementsByTagName(elementName);
|
||||
if (nodeList != null && nodeList.getLength() > 0) {
|
||||
return Optional.of(nodeList.item(0).getTextContent());
|
||||
}
|
||||
} catch (SOAPException e) {
|
||||
// if an error occurs, returning an empty Optional is fine
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="tr064" 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>TR-064 Binding</name>
|
||||
<description>This is the binding for TR-064 device support.</description>
|
||||
<author>Jan N. Klug</author>
|
||||
|
||||
</binding:binding>
|
@ -0,0 +1,16 @@
|
||||
<?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="profile:transform:PHONEBOOK">
|
||||
<parameter name="phonebook" type="text" required="true">
|
||||
<label>Phonebook</label>
|
||||
<description>The name of the the phonebook</description>
|
||||
</parameter>
|
||||
<parameter name="matchCount" type="text" required="false">
|
||||
<label>Match Count</label>
|
||||
<description>The number of digits matching the incoming value, counted from far right (default is 0 = all matching)</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
@ -0,0 +1,145 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="tr064"
|
||||
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">
|
||||
|
||||
<bridge-type id="generic">
|
||||
<label>Generic CPE</label>
|
||||
|
||||
<config-description>
|
||||
<parameter name="host" type="text" required="true">
|
||||
<label>Host</label>
|
||||
<description>Host name or IP address.</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="user" type="text">
|
||||
<label>Username</label>
|
||||
<default>dslf-config</default>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="true">
|
||||
<label>Password</label>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="refresh" type="integer" unit="s">
|
||||
<label>Refresh Interval</label>
|
||||
<default>60</default>
|
||||
<unitLabel>s</unitLabel>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
<bridge-type id="fritzbox">
|
||||
<label>FritzBox</label>
|
||||
<description>A physical FritzBox Device.</description>
|
||||
|
||||
<config-description>
|
||||
<parameter name="host" type="text" required="true">
|
||||
<label>Host</label>
|
||||
<description>Host name or IP address.</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="user" type="text">
|
||||
<label>Username</label>
|
||||
<default>dslf-config</default>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="true">
|
||||
<label>Password</label>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="refresh" type="integer" unit="s">
|
||||
<label>Refresh Interval</label>
|
||||
<default>60</default>
|
||||
<unitLabel>s</unitLabel>
|
||||
</parameter>
|
||||
<parameter name="tamIndices" type="text" multiple="true">
|
||||
<label>TAM</label>
|
||||
<description>List of answering machines (starting with 0).</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="callDeflectionIndices" type="text" multiple="true">
|
||||
<label>Call Deflection</label>
|
||||
<description>List of call deflection IDs (starting with 0).</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="missedCallDays" type="text" multiple="true">
|
||||
<label>Missed Call Days</label>
|
||||
<description>List of days for which missed calls should be calculated.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="rejectedCallDays" type="text" multiple="true">
|
||||
<label>Rejected Call Days</label>
|
||||
<description>List of days for which rejected calls should be calculated.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="inboundCallDays" type="text" multiple="true">
|
||||
<label>Inbound Call Days</label>
|
||||
<description>List of days for which inbound calls should be calculated.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="outboundCallDays" type="text" multiple="true">
|
||||
<label>Outbound Call Days</label>
|
||||
<description>List of days for which outbound calls should be calculated.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="wanBlockIPs" type="text" multiple="true">
|
||||
<label>WAN Block IPs</label>
|
||||
<description>List of IPs that can be blocked for WAN access.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="phonebookInterval" type="integer" min="0" unit="s">
|
||||
<label>Phonebook Interval</label>
|
||||
<description>The interval for refreshing the phonebook (disabled = 0)</description>
|
||||
<default>600</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
<thing-type id="subdevice">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="generic"/>
|
||||
<bridge-type-ref id="fritzbox"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Sub-Device</label>
|
||||
<description>A virtual sub-device.</description>
|
||||
|
||||
<config-description>
|
||||
<parameter name="uuid" type="text" required="true">
|
||||
<label>UUID</label>
|
||||
<description>UUID of the sub-device</description>
|
||||
</parameter>
|
||||
<parameter name="refresh" type="integer" unit="s">
|
||||
<label>Refresh Interval</label>
|
||||
<default>60</default>
|
||||
<unitLabel>s</unitLabel>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="subdeviceLan" listed="false">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="generic"/>
|
||||
<bridge-type-ref id="fritzbox"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Sub-Device (LAN)</label>
|
||||
<description>A virtual Sub-Device (LAN).</description>
|
||||
|
||||
<config-description>
|
||||
<parameter name="uuid" type="text" required="true">
|
||||
<label>UUID</label>
|
||||
<description>UUID of the sub-device</description>
|
||||
</parameter>
|
||||
<parameter name="refresh" type="integer" unit="s">
|
||||
<label>Refresh Interval</label>
|
||||
<default>60</default>
|
||||
<unitLabel>s</unitLabel>
|
||||
</parameter>
|
||||
<parameter name="macOnline" type="text" multiple="true">
|
||||
<label>MAC Online</label>
|
||||
<description>List of MACs for "online" status detection (format: 11:11:11:11:11:11).</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,253 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<channels xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="channelconfig"
|
||||
xsi:noNamespaceSchemaLocation="xsd/channeltypes.xsd">
|
||||
<!-- Root Device -->
|
||||
<channel name="securityPort" label="Security Port"
|
||||
description="The port for connecting via HTTPS to the TR-064 service." advanced="true">
|
||||
<item type="Number"></item>
|
||||
<service deviceType="urn:dslforum-org:device:InternetGatewayDevice:1"
|
||||
serviceId="urn:DeviceInfo-com:serviceId:DeviceInfo1"></service>
|
||||
<getAction name="GetSecurityPort" argument="NewSecurityPort"/>
|
||||
</channel>
|
||||
<channel name="uptime" label="Uptime">
|
||||
<item type="Number:Time" unit="s" statePattern="%d s"/>
|
||||
<service deviceType="urn:dslforum-org:device:InternetGatewayDevice:1"
|
||||
serviceId="urn:DeviceInfo-com:serviceId:DeviceInfo1"/>
|
||||
<getAction name="GetInfo" argument="NewUpTime"/>
|
||||
</channel>
|
||||
<channel name="deviceLog" label="Device Log" description="A string containing the last log messages.">
|
||||
<item type="String"/>
|
||||
<service deviceType="urn:dslforum-org:device:InternetGatewayDevice:1"
|
||||
serviceId="urn:DeviceInfo-com:serviceId:DeviceInfo1"/>
|
||||
<getAction name="GetInfo" argument="NewDeviceLog"/>
|
||||
</channel>
|
||||
<channel name="reboot" label="Reboot">
|
||||
<item type="Switch"/>
|
||||
<service deviceType="urn:dslforum-org:device:InternetGatewayDevice:1"
|
||||
serviceId="urn:DeviceConfig-com:serviceId:DeviceConfig1"/>
|
||||
<setAction name="Reboot"/>
|
||||
</channel>
|
||||
<channel name="tamEnable" label="TAM" description="Enable/Disable the answering machine with the given index.">
|
||||
<item type="Switch"/>
|
||||
<service deviceType="urn:dslforum-org:device:InternetGatewayDevice:1"
|
||||
serviceId="urn:X_AVM-DE_TAM-com:serviceId:X_AVM-DE_TAM1"/>
|
||||
<getAction name="GetInfo" argument="NewEnable">
|
||||
<parameter name="NewIndex" thingParameter="tamIndices"/>
|
||||
</getAction>
|
||||
<setAction name="SetEnable" argument="NewEnable">
|
||||
<parameter name="NewIndex" thingParameter="tamIndices"/>
|
||||
</setAction>
|
||||
</channel>
|
||||
<channel name="tamNewMessages" label="TAM New Messages"
|
||||
description="The number of new messages of the given answering machine.">
|
||||
<item type="Number"/>
|
||||
<service deviceType="urn:dslforum-org:device:InternetGatewayDevice:1"
|
||||
serviceId="urn:X_AVM-DE_TAM-com:serviceId:X_AVM-DE_TAM1"/>
|
||||
<getAction name="GetMessageList" argument="NewURL" postProcessor="processTamListURL">
|
||||
<parameter name="NewIndex" thingParameter="tamIndices"/>
|
||||
</getAction>
|
||||
</channel>
|
||||
<channel name="callDeflectionEnable" label="Call Deflection"
|
||||
description="Enable/Disable the call deflection setup with the given index.">
|
||||
<item type="Switch"/>
|
||||
<service deviceType="urn:dslforum-org:device:InternetGatewayDevice:1"
|
||||
serviceId="urn:X_AVM-DE_OnTel-com:serviceId:X_AVM-DE_OnTel1"/>
|
||||
<getAction name="GetDeflection" argument="NewEnable">
|
||||
<parameter name="NewDeflectionId" thingParameter="callDeflectionIndices"/>
|
||||
</getAction>
|
||||
<setAction name="SetDeflectionEnable" argument="NewEnable">
|
||||
<parameter name="NewDeflectionId" thingParameter="callDeflectionIndices"/>
|
||||
</setAction>
|
||||
</channel>
|
||||
<channel name="wanBlockByIP" label="WAN Block By IP" description="Block/Unblock WAN access with the given IP.">
|
||||
<item type="Switch"/>
|
||||
<service deviceType="urn:dslforum-org:device:InternetGatewayDevice:1"
|
||||
serviceId="urn:X_AVM-DE_HostFilter-com:serviceId:X_AVM-DE_HostFilter1"/>
|
||||
<getAction name="GetWANAccessByIP" argument="NewDisallow">
|
||||
<parameter name="NewIPv4Address" thingParameter="wanBlockIPs"/>
|
||||
</getAction>
|
||||
<setAction name="DisallowWANAccessByIP" argument="NewDisallow">
|
||||
<parameter name="NewIPv4Address" thingParameter="wanBlockIPs"/>
|
||||
</setAction>
|
||||
</channel>
|
||||
<channel name="missedCalls" label="Missed Calls"
|
||||
description="Number of missed calls within the given number of days.">
|
||||
<item type="Number"/>
|
||||
<service deviceType="urn:dslforum-org:device:InternetGatewayDevice:1"
|
||||
serviceId="urn:X_AVM-DE_OnTel-com:serviceId:X_AVM-DE_OnTel1"/>
|
||||
<getAction name="GetCallList" argument="NewCallListURL" postProcessor="processMissedCalls">
|
||||
<parameter name="CallDays" thingParameter="missedCallDays" pattern="[0-9]+" internalOnly="true"/>
|
||||
</getAction>
|
||||
</channel>
|
||||
<channel name="rejectedCalls" label="Rejected Calls"
|
||||
description="Number of rejected calls within the given number of days.">
|
||||
<item type="Number"/>
|
||||
<service deviceType="urn:dslforum-org:device:InternetGatewayDevice:1"
|
||||
serviceId="urn:X_AVM-DE_OnTel-com:serviceId:X_AVM-DE_OnTel1"/>
|
||||
<getAction name="GetCallList" argument="NewCallListURL" postProcessor="processRejectedCalls">
|
||||
<parameter name="CallDays" thingParameter="rejectedCallDays" pattern="[0-9]+" internalOnly="true"/>
|
||||
</getAction>
|
||||
</channel>
|
||||
<channel name="inboundCalls" label="Inbound Calls"
|
||||
description="Number of inbound calls within the given number of days.">
|
||||
<item type="Number"/>
|
||||
<service deviceType="urn:dslforum-org:device:InternetGatewayDevice:1"
|
||||
serviceId="urn:X_AVM-DE_OnTel-com:serviceId:X_AVM-DE_OnTel1"/>
|
||||
<getAction name="GetCallList" argument="NewCallListURL" postProcessor="processInboundCalls">
|
||||
<parameter name="CallDays" thingParameter="inboundCallDays" pattern="[0-9]+" internalOnly="true"/>
|
||||
</getAction>
|
||||
</channel>
|
||||
<channel name="outboundCalls" label="Outbound Calls"
|
||||
description="Number of outbound calls within the given number of days.">
|
||||
<item type="Number"/>
|
||||
<service deviceType="urn:dslforum-org:device:InternetGatewayDevice:1"
|
||||
serviceId="urn:X_AVM-DE_OnTel-com:serviceId:X_AVM-DE_OnTel1"/>
|
||||
<getAction name="GetCallList" argument="NewCallListURL" postProcessor="processOutboundCalls">
|
||||
<parameter name="CallDays" thingParameter="outboundCallDays" pattern="[0-9]+" internalOnly="true"/>
|
||||
</getAction>
|
||||
</channel>
|
||||
|
||||
<!-- LAN Device -->
|
||||
<channel name="wifi24GHzEnable" label="WiFi 2.4 GHz" description="Enable/Disable the 2.4 GHz WiFi device.">
|
||||
<item type="Switch"/>
|
||||
<service deviceType="urn:dslforum-org:device:LANDevice:1"
|
||||
serviceId="urn:WLANConfiguration-com:serviceId:WLANConfiguration1"/>
|
||||
<getAction name="GetInfo" argument="NewEnable"/>
|
||||
<setAction name="SetEnable" argument="NewEnable"/>
|
||||
</channel>
|
||||
<channel name="wifi5GHzEnable" label="WiFi 5 GHz" description="Enable/Disable the 5.0 GHz WiFi device.">
|
||||
<item type="Switch"/>
|
||||
<service deviceType="urn:dslforum-org:device:LANDevice:1"
|
||||
serviceId="urn:WLANConfiguration-com:serviceId:WLANConfiguration2"/>
|
||||
<getAction name="GetInfo" argument="NewEnable"/>
|
||||
<setAction name="SetEnable" argument="NewEnable"/>
|
||||
</channel>
|
||||
<channel name="wifiGuestEnable" label="WiFi Guest" description="Enable/Disable the guest WiFi.">
|
||||
<item type="Switch"/>
|
||||
<service deviceType="urn:dslforum-org:device:LANDevice:1"
|
||||
serviceId="urn:WLANConfiguration-com:serviceId:WLANConfiguration3"/>
|
||||
<getAction name="GetInfo" argument="NewEnable"/>
|
||||
<setAction name="SetEnable" argument="NewEnable"/>
|
||||
</channel>
|
||||
<channel name="macOnline" label="MAC Online" description="Online status of the device with the given MAC">
|
||||
<item type="Switch"/>
|
||||
<service deviceType="urn:dslforum-org:device:LANDevice:1" serviceId="urn:LanDeviceHosts-com:serviceId:Hosts1"/>
|
||||
<getAction name="GetSpecificHostEntry" argument="NewActive">
|
||||
<parameter name="NewMACAddress" thingParameter="macOnline" pattern="([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}"/>
|
||||
</getAction>
|
||||
</channel>
|
||||
|
||||
<!-- WAN Device -->
|
||||
<channel name="wanAccessType" label="Access Type">
|
||||
<item type="String"/>
|
||||
<service deviceType="urn:dslforum-org:device:WANDevice:1"
|
||||
serviceId="urn:WANCIfConfig-com:serviceId:WANCommonInterfaceConfig1"/>
|
||||
<getAction name="GetCommonLinkProperties" argument="NewWANAccessType"/>
|
||||
</channel>
|
||||
<channel name="wanPhysicalLinkStatus" label="Link Status">
|
||||
<item type="String"/>
|
||||
<service deviceType="urn:dslforum-org:device:WANDevice:1"
|
||||
serviceId="urn:WANCIfConfig-com:serviceId:WANCommonInterfaceConfig1"/>
|
||||
<getAction name="GetCommonLinkProperties" argument="NewPhysicalLinkStatus"/>
|
||||
</channel>
|
||||
<channel name="wanMaxDownstreamRate" label="Max Downstream Rate">
|
||||
<item type="Number:DataTransferRate" unit="bit/s" statePattern="%.1f Mbit/s"/>
|
||||
<service deviceType="urn:dslforum-org:device:WANDevice:1"
|
||||
serviceId="urn:WANCIfConfig-com:serviceId:WANCommonInterfaceConfig1"/>
|
||||
<getAction name="GetCommonLinkProperties" argument="NewLayer1DownstreamMaxBitRate"/>
|
||||
</channel>
|
||||
<channel name="wanMaxUpstreamRate" label="Max Upstream Rate">
|
||||
<item type="Number:DataTransferRate" unit="bit/s" statePattern="%.1f Mbit/s"/>
|
||||
<service deviceType="urn:dslforum-org:device:WANDevice:1"
|
||||
serviceId="urn:WANCIfConfig-com:serviceId:WANCommonInterfaceConfig1"/>
|
||||
<getAction name="GetCommonLinkProperties" argument="NewLayer1UpstreamMaxBitRate"/>
|
||||
</channel>
|
||||
<channel name="wanTotalBytesReceived" label="Total Bytes Received">
|
||||
<item type="Number:DataAmount" unit="B" statePattern="%.3f Gio"/>
|
||||
<service deviceType="urn:dslforum-org:device:WANDevice:1"
|
||||
serviceId="urn:WANCIfConfig-com:serviceId:WANCommonInterfaceConfig1"/>
|
||||
<getAction name="GetTotalBytesReceived" argument="NewTotalBytesReceived"/>
|
||||
</channel>
|
||||
<channel name="wanTotalBytesSent" label="Total Bytes Send">
|
||||
<item type="Number:DataAmount" unit="B" statePattern="%.3f Gio"/>
|
||||
<service deviceType="urn:dslforum-org:device:WANDevice:1"
|
||||
serviceId="urn:WANCIfConfig-com:serviceId:WANCommonInterfaceConfig1"/>
|
||||
<getAction name="GetTotalBytesSent" argument="NewTotalBytesSent"/>
|
||||
</channel>
|
||||
<channel name="dslEnable" label="DSL Enable">
|
||||
<item type="Switch"/>
|
||||
<service deviceType="urn:dslforum-org:service:WANDSLInterfaceConfig:1"
|
||||
serviceId="urn:WANDSLIfConfig-com:serviceId:WANDSLInterfaceConfig1"/>
|
||||
<getAction name="GetInfo" argument="NewEnable"/>
|
||||
</channel>
|
||||
<channel name="dslStatus" label="DSL Status">
|
||||
<item type="Switch"/>
|
||||
<service deviceType="urn:dslforum-org:service:WANDSLInterfaceConfig:1"
|
||||
serviceId="urn:WANDSLIfConfig-com:serviceId:WANDSLInterfaceConfig1"/>
|
||||
<getAction name="GetInfo" argument="NewStatus"/>
|
||||
</channel>
|
||||
<channel name="dslDownstreamNoiseMargin" label="DSL Downstream Noise Margin">
|
||||
<item type="Number:Dimensionless" unit="dB" statePattern="%.1f dB"/>
|
||||
<service deviceType="urn:dslforum-org:service:WANDSLInterfaceConfig:1"
|
||||
serviceId="urn:WANDSLIfConfig-com:serviceId:WANDSLInterfaceConfig1"/>
|
||||
<getAction name="GetInfo" argument="NewDownstreamNoiseMargin"/>
|
||||
</channel>
|
||||
<channel name="dslUpstreamNoiseMargin" label="DSL Upstream Noise Margin">
|
||||
<item type="Number:Dimensionless" unit="dB" statePattern="%.1f dB"/>
|
||||
<service deviceType="urn:dslforum-org:service:WANDSLInterfaceConfig:1"
|
||||
serviceId="urn:WANDSLIfConfig-com:serviceId:WANDSLInterfaceConfig1"/>
|
||||
<getAction name="GetInfo" argument="NewUpstreamNoiseMargin"/>
|
||||
</channel>
|
||||
<channel name="dslDownstreamNoiseMargin" label="DSL Downstream Attenuation">
|
||||
<item type="Number:Dimensionless" unit="dB" statePattern="%.1f dB"/>
|
||||
<service deviceType="urn:dslforum-org:service:WANDSLInterfaceConfig:1"
|
||||
serviceId="urn:WANDSLIfConfig-com:serviceId:WANDSLInterfaceConfig1"/>
|
||||
<getAction name="GetInfo" argument="NewDownstreamAttenuation"/>
|
||||
</channel>
|
||||
<channel name="dslUpstreamNoiseMargin" label="DSL Upstream Attenuation">
|
||||
<item type="Number:Dimensionless" unit="dB" statePattern="%.1f dB"/>
|
||||
<service deviceType="urn:dslforum-org:service:WANDSLInterfaceConfig:1"
|
||||
serviceId="urn:WANDSLIfConfig-com:serviceId:WANDSLInterfaceConfig1"/>
|
||||
<getAction name="GetInfo" argument="NewUpstreamAttenuation"/>
|
||||
</channel>
|
||||
<channel name="dslFECErrors" label="DSL FEC Errors">
|
||||
<item type="Number:Dimensionless"/>
|
||||
<service deviceType="urn:dslforum-org:service:WANDSLInterfaceConfig:1"
|
||||
serviceId="urn:WANDSLIfConfig-com:serviceId:WANDSLInterfaceConfig1"/>
|
||||
<getAction name="GetStatisticsTotal" argument="NewFECErrors"/>
|
||||
</channel>
|
||||
<channel name="dslHECErrors" label="DSL HEC Errors">
|
||||
<item type="Number:Dimensionless"/>
|
||||
<service deviceType="urn:dslforum-org:service:WANDSLInterfaceConfig:1"
|
||||
serviceId="urn:WANDSLIfConfig-com:serviceId:WANDSLInterfaceConfig1"/>
|
||||
<getAction name="GetStatisticsTotal" argument="NewHECErrors"/>
|
||||
</channel>
|
||||
<channel name="dslCRCErrors" label="DSL CRC Errors">
|
||||
<item type="Number:Dimensionless"/>
|
||||
<service deviceType="urn:dslforum-org:service:WANDSLInterfaceConfig:1"
|
||||
serviceId="urn:WANDSLIfConfig-com:serviceId:WANDSLInterfaceConfig1"/>
|
||||
<getAction name="GetStatisticsTotal" argument="NeCRCErrors"/>
|
||||
</channel>
|
||||
|
||||
<!-- WAN Connection device -->
|
||||
<channel name="wanIpAddress" label="WAN IP Address">
|
||||
<item type="String"/>
|
||||
<service deviceType="urn:dslforum-org:device:WANConnectionDevice:1"
|
||||
serviceId="urn:WANIPConnection-com:serviceId:WANIPConnection1"/>
|
||||
<getAction name="GetInfo" argument="NewExternalIPAddress"/>
|
||||
</channel>
|
||||
<channel name="wanConnectionStatus" label="Connection Status">
|
||||
<item type="String"/>
|
||||
<service deviceType="urn:dslforum-org:device:WANConnectionDevice:1"
|
||||
serviceId="urn:WANIPConnection-com:serviceId:WANIPConnection1"/>
|
||||
<getAction name="GetInfo" argument="NewConnectionStatus"/>
|
||||
</channel>
|
||||
<channel name="uptime" label="Uptime">
|
||||
<item type="Number:Time" unit="s" statePattern="%d s"/>
|
||||
<service deviceType="urn:dslforum-org:device:WANConnectionDevice:1"
|
||||
serviceId="urn:WANIPConnection-com:serviceId:WANIPConnection1"/>
|
||||
<getAction name="GetInfo" argument="NewUptime"/>
|
||||
</channel>
|
||||
|
||||
|
||||
</channels>
|
@ -0,0 +1,39 @@
|
||||
<jaxb:bindings xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
|
||||
version="2.0">
|
||||
<jaxb:globalBindings>
|
||||
<xjc:serializable uid="1"/>
|
||||
</jaxb:globalBindings>
|
||||
|
||||
<jaxb:bindings schemaLocation="phonebook.xsd">
|
||||
<jaxb:schemaBindings>
|
||||
<jaxb:package name="org.openhab.binding.tr064.internal.dto.phonebook"/>
|
||||
</jaxb:schemaBindings>
|
||||
</jaxb:bindings>
|
||||
|
||||
<jaxb:bindings schemaLocation="channeltypes.xsd">
|
||||
<jaxb:schemaBindings>
|
||||
<jaxb:package name="org.openhab.binding.tr064.internal.dto.config"/>
|
||||
</jaxb:schemaBindings>
|
||||
</jaxb:bindings>
|
||||
|
||||
<jaxb:bindings schemaLocation="scpdservice.xsd">
|
||||
<jaxb:schemaBindings>
|
||||
<jaxb:package name="org.openhab.binding.tr064.internal.dto.scpd.service"/>
|
||||
<jaxb:nameXmlTransform>
|
||||
<jaxb:typeName prefix="SCPD"/>
|
||||
<jaxb:anonymousTypeName prefix="SCPD"/>
|
||||
</jaxb:nameXmlTransform>
|
||||
</jaxb:schemaBindings>
|
||||
</jaxb:bindings>
|
||||
|
||||
<jaxb:bindings schemaLocation="scpddevice.xsd">
|
||||
<jaxb:schemaBindings>
|
||||
<jaxb:package name="org.openhab.binding.tr064.internal.dto.scpd.root"/>
|
||||
<jaxb:nameXmlTransform>
|
||||
<jaxb:typeName prefix="SCPD"/>
|
||||
<jaxb:anonymousTypeName prefix="SCPD"/>
|
||||
</jaxb:nameXmlTransform>
|
||||
</jaxb:schemaBindings>
|
||||
</jaxb:bindings>
|
||||
|
||||
</jaxb:bindings>
|
@ -0,0 +1,56 @@
|
||||
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="channelconfig" targetNamespace="channelconfig" xmlns:xa="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:element name="channels" type="channelTypeDescriptions"/>
|
||||
<xs:complexType name="itemType">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:string">
|
||||
<xs:attribute type="xs:string" name="type" use="required"/>
|
||||
<xs:attribute type="xs:string" name="unit" default=""/>
|
||||
<xs:attribute type="xs:string" name="statePattern"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="serviceType">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:string">
|
||||
<xs:attribute type="xs:string" name="deviceType" use="required"/>
|
||||
<xs:attribute type="xs:string" name="serviceId" use="required"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="parameterType">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:string">
|
||||
<xs:attribute type="xs:string" name="name" use="required"/>
|
||||
<xs:attribute type="xs:string" name="thingParameter" use="required"/>
|
||||
<xs:attribute type="xs:string" name="pattern"/>
|
||||
<xs:attribute type="xs:boolean" name="internalOnly" default="false"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="actionType">
|
||||
<xs:sequence>
|
||||
<xs:element type="parameterType" name="parameter" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute type="xs:string" name="name" use="required"/>
|
||||
<xs:attribute type="xs:string" name="argument"/>
|
||||
<xs:attribute type="xs:string" name="postProcessor"/>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="channelTypeDescription">
|
||||
<xs:sequence>
|
||||
<xs:element type="itemType" name="item"/>
|
||||
<xs:element type="serviceType" name="service"/>
|
||||
<xs:element type="actionType" name="getAction" minOccurs="0"/>
|
||||
<xs:element type="actionType" name="setAction" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute type="xs:string" name="name" use="required"/>
|
||||
<xs:attribute type="xs:string" name="label"/>
|
||||
<xs:attribute type="xs:string" name="description"/>
|
||||
<xs:attribute type="xs:boolean" name="advanced" default="false"/>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="channelTypeDescriptions">
|
||||
<xs:sequence>
|
||||
<xs:element type="channelTypeDescription" name="channel" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:schema>
|
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:element name="phonebooks" type="phonebooksType"/>
|
||||
<xs:complexType name="personType">
|
||||
<xs:sequence>
|
||||
<xs:element type="xs:string" name="realName"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="numberType">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:string">
|
||||
<xs:attribute type="xs:string" name="type" use="optional"/>
|
||||
<xs:attribute type="xs:string" name="vanity" use="optional"/>
|
||||
<xs:attribute type="xs:string" name="prio" use="optional"/>
|
||||
<xs:attribute type="xs:byte" name="quickdial" use="optional"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="telephonyType">
|
||||
<xs:sequence>
|
||||
<xs:element type="xs:string" name="services"/>
|
||||
<xs:element type="numberType" name="number" maxOccurs="unbounded" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="contactType">
|
||||
<xs:sequence>
|
||||
<xs:element name="category">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:byte">
|
||||
<xs:enumeration value="0"/>
|
||||
<xs:enumeration value="1"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:element>
|
||||
<xs:element type="personType" name="person"/>
|
||||
<xs:element type="xs:byte" name="uniqueid"/>
|
||||
<xs:element type="telephonyType" name="telephony"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="phonebookType">
|
||||
<xs:sequence>
|
||||
<xs:element type="xs:int" name="timestamp"/>
|
||||
<xs:element type="contactType" name="contact" maxOccurs="unbounded" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute type="xs:byte" name="owner"/>
|
||||
<xs:attribute type="xs:string" name="name"/>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="phonebooksType">
|
||||
<xs:sequence>
|
||||
<xs:element type="phonebookType" name="phonebook"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:schema>
|
@ -0,0 +1,79 @@
|
||||
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="urn:dslforum-org:device-1-0" targetNamespace="urn:dslforum-org:device-1-0">
|
||||
<xs:element name="root" type="rootType"/>
|
||||
<xs:complexType name="specVersionType">
|
||||
<xs:sequence>
|
||||
<xs:element type="xs:byte" name="major"/>
|
||||
<xs:element type="xs:byte" name="minor"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="systemVersionType">
|
||||
<xs:sequence>
|
||||
<xs:element type="xs:short" name="HW"/>
|
||||
<xs:element type="xs:short" name="Major"/>
|
||||
<xs:element type="xs:byte" name="Minor"/>
|
||||
<xs:element type="xs:byte" name="Patch"/>
|
||||
<xs:element type="xs:int" name="Buildnumber"/>
|
||||
<xs:element type="xs:string" name="Display"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="iconType">
|
||||
<xs:sequence>
|
||||
<xs:element type="xs:string" name="mimetype"/>
|
||||
<xs:element type="xs:byte" name="width"/>
|
||||
<xs:element type="xs:byte" name="height"/>
|
||||
<xs:element type="xs:byte" name="depth"/>
|
||||
<xs:element type="xs:string" name="url"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="iconListType">
|
||||
<xs:sequence>
|
||||
<xs:element type="iconType" name="icon"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="serviceType">
|
||||
<xs:sequence>
|
||||
<xs:element type="xs:string" name="serviceType"/>
|
||||
<xs:element type="xs:string" name="serviceId"/>
|
||||
<xs:element type="xs:string" name="controlURL"/>
|
||||
<xs:element type="xs:string" name="eventSubURL"/>
|
||||
<xs:element type="xs:string" name="SCPDURL"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="serviceListType">
|
||||
<xs:sequence>
|
||||
<xs:element type="serviceType" name="service" maxOccurs="unbounded" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="deviceType">
|
||||
<xs:sequence>
|
||||
<xs:element type="xs:string" name="deviceType"/>
|
||||
<xs:element type="xs:string" name="friendlyName"/>
|
||||
<xs:element type="xs:string" name="manufacturer"/>
|
||||
<xs:element type="xs:anyURI" name="manufacturerURL"/>
|
||||
<xs:element type="xs:string" name="modelDescription"/>
|
||||
<xs:element type="xs:string" name="modelName"/>
|
||||
<xs:element type="xs:string" name="modelNumber"/>
|
||||
<xs:element type="xs:anyURI" name="modelURL"/>
|
||||
<xs:element type="xs:string" name="UDN"/>
|
||||
<xs:element type="xs:string" name="UPC" minOccurs="0"/>
|
||||
<xs:element type="iconListType" name="iconList" minOccurs="0"/>
|
||||
<xs:element type="serviceListType" name="serviceList"/>
|
||||
<xs:element type="deviceListType" name="deviceList" minOccurs="0"/>
|
||||
<xs:element type="xs:anyURI" name="presentationURL" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="deviceListType">
|
||||
<xs:sequence>
|
||||
<xs:element type="deviceType" name="device" maxOccurs="unbounded" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="rootType">
|
||||
<xs:sequence>
|
||||
<xs:element type="specVersionType" name="specVersion"/>
|
||||
<xs:element type="systemVersionType" name="systemVersion"/>
|
||||
<xs:element type="deviceType" name="device"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:schema>
|
||||
|
@ -0,0 +1,73 @@
|
||||
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="urn:dslforum-org:service-1-0" targetNamespace="urn:dslforum-org:service-1-0">
|
||||
<xs:element name="scpd" type="scpdType"/>
|
||||
<xs:complexType name="specVersionType">
|
||||
<xs:sequence>
|
||||
<xs:element type="xs:byte" name="major"/>
|
||||
<xs:element type="xs:byte" name="minor"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:simpleType name="direction">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="in"/>
|
||||
<xs:enumeration value="out"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:complexType name="argumentType">
|
||||
<xs:sequence>
|
||||
<xs:element type="xs:string" name="name" minOccurs="0"/>
|
||||
<xs:element type="direction" name="direction" minOccurs="0"/>
|
||||
<xs:element type="xs:string" name="relatedStateVariable" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="argumentListType">
|
||||
<xs:sequence>
|
||||
<xs:element type="argumentType" name="argument" maxOccurs="unbounded" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="actionType">
|
||||
<xs:sequence>
|
||||
<xs:element type="xs:string" name="name"/>
|
||||
<xs:element type="argumentListType" name="argumentList"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="actionListType">
|
||||
<xs:sequence>
|
||||
<xs:element type="actionType" name="action" maxOccurs="unbounded" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="stateVariableType">
|
||||
<xs:sequence>
|
||||
<xs:element type="xs:string" name="name"/>
|
||||
<xs:element type="xs:string" name="dataType"/>
|
||||
<xs:element type="xs:string" name="defaultValue" minOccurs="0"/>
|
||||
<xs:element type="allowedValueRangeType" name="allowedValueRange" minOccurs="0"/>
|
||||
<xs:element type="allowedValueListType" name="allowedValueList" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute type="xs:string" name="sendEvents"/>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="allowedValueRangeType">
|
||||
<xs:sequence>
|
||||
<xs:element type="xs:byte" name="minimum"/>
|
||||
<xs:element type="xs:byte" name="maximum"/>
|
||||
<xs:element type="xs:byte" name="step"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="allowedValueListType">
|
||||
<xs:sequence>
|
||||
<xs:element type="xs:string" name="allowedValue" maxOccurs="unbounded" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="serviceStateTableType">
|
||||
<xs:sequence>
|
||||
<xs:element type="stateVariableType" name="stateVariable" maxOccurs="unbounded" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="scpdType">
|
||||
<xs:sequence>
|
||||
<xs:element type="specVersionType" name="specVersion"/>
|
||||
<xs:element type="actionListType" name="actionList"/>
|
||||
<xs:element type="serviceStateTableType" name="serviceStateTable"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:schema>
|
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 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.tr064;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.tr064.internal.Tr064BindingConstants;
|
||||
import org.openhab.binding.tr064.internal.dto.config.ChannelTypeDescription;
|
||||
|
||||
/**
|
||||
* The {@link ChannelListUtilTest} is a tool for documentation generation
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ChannelListUtilTest {
|
||||
|
||||
@Test
|
||||
public void createChannelListTest() {
|
||||
try {
|
||||
final Writer writer = new OutputStreamWriter(new FileOutputStream("target/channelList.asc"),
|
||||
StandardCharsets.UTF_8);
|
||||
|
||||
Tr064BindingConstants.CHANNEL_TYPES.stream().sorted(Comparator.comparing(ChannelTypeDescription::getName))
|
||||
.forEach(channel -> {
|
||||
String description = channel.getDescription() == null ? channel.getLabel()
|
||||
: channel.getDescription();
|
||||
String channelString = String.format("| `%s` | `%s`| %s |%c", channel.getName(),
|
||||
channel.getItem().getType(), description, 13);
|
||||
try {
|
||||
writer.write(channelString);
|
||||
} catch (IOException e) {
|
||||
Assertions.fail(e.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
writer.close();
|
||||
} catch (IOException e) {
|
||||
Assertions.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -277,6 +277,7 @@
|
||||
<module>org.openhab.binding.tibber</module>
|
||||
<module>org.openhab.binding.touchwand</module>
|
||||
<module>org.openhab.binding.tplinksmarthome</module>
|
||||
<module>org.openhab.binding.tr064</module>
|
||||
<module>org.openhab.binding.tradfri</module>
|
||||
<module>org.openhab.binding.unifi</module>
|
||||
<module>org.openhab.binding.unifiedremote</module>
|
||||
|
Loading…
Reference in New Issue
Block a user