mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 07:02:02 +01:00
[http] Initial contribution (#8521)
Signed-off-by: Jan N. Klug <jan.n.klug@rub.de>
This commit is contained in:
parent
3a4eaae1e9
commit
8de5652ed1
@ -89,6 +89,7 @@
|
||||
/bundles/org.openhab.binding.heos/ @Wire82
|
||||
/bundles/org.openhab.binding.homematic/ @FStolte @gerrieg @mdicke2s
|
||||
/bundles/org.openhab.binding.hpprinter/ @cossey
|
||||
/bundles/org.openhab.binding.http/ @J-N-K
|
||||
/bundles/org.openhab.binding.hue/ @cweitkamp
|
||||
/bundles/org.openhab.binding.hydrawise/ @digitaldan
|
||||
/bundles/org.openhab.binding.hyperion/ @tavalin
|
||||
|
@ -436,6 +436,11 @@
|
||||
<artifactId>org.openhab.binding.hpprinter</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.http</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.hue</artifactId>
|
||||
|
13
bundles/org.openhab.binding.http/NOTICE
Normal file
13
bundles/org.openhab.binding.http/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/openhab-addons
|
146
bundles/org.openhab.binding.http/README.md
Normal file
146
bundles/org.openhab.binding.http/README.md
Normal file
@ -0,0 +1,146 @@
|
||||
# HTTP Binding
|
||||
|
||||
This binding allows using HTTP to bring external data into openHAB or execute HTTP requests on commands.
|
||||
|
||||
## Supported Things
|
||||
|
||||
Only one thing named `url` is available.
|
||||
It can be extended with different channels.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
| parameter | optional | default | description |
|
||||
|-------------------|----------|---------|-------------|
|
||||
| `baseURL` | no | - | The base URL for this thing. Can be extended in channel-configuration. |
|
||||
| `refresh` | no | 30 | Time in seconds between two refresh calls for the channels of this thing. |
|
||||
| `timeout` | no | 3000 | Timeout for HTTP requests in ms. |
|
||||
| `username` | yes | - | Username for authentication (advanced parameter). |
|
||||
| `password` | yes | - | Password for authentication (advanced parameter). |
|
||||
| `authMode` | no | BASIC | Authentication mode, `BASIC` or `DIGEST` (advanced parameter). |
|
||||
| `commandMethod` | no | GET | Method used for sending commands `GET`, `PUT`, `POST`. |
|
||||
| `contentType` | yes | - | MIME content-type of the command requests. Only used for `PUT` and `POST`. |
|
||||
| `encoding` | yes | - | Encoding to be used if no encoding is found in responses (advanced parameter). |
|
||||
| `headers` | yes | - | Additional headers that are sent along with the request. Format is "header=value".|
|
||||
| `ignoreSSLErrors` | no | false | If set to true ignores invalid SSL certificate errors. This is potentially dangerous.|
|
||||
|
||||
*Note:* optional "no" means that you have to configure a value unless a default is provided and you are ok with that setting.
|
||||
|
||||
## Channels
|
||||
|
||||
Each item type has its own channel-type.
|
||||
Depending on the channel-type, channels have different configuration options.
|
||||
All channel-types (except `image`) have `stateExtension`, `commandExtension`, `stateTransformation`, `commandTransformation` and `mode` parameters.
|
||||
The `image` channel-type supports `stateExtension` only.
|
||||
|
||||
| parameter | optional | default | description |
|
||||
|-------------------------|----------|-------------|-------------|
|
||||
| `stateExtension` | yes | - | Appended to the `baseURL` for requesting states. |
|
||||
| `commandExtension` | yes | - | Appended to the `baseURL` for sending commands. If empty, same as `stateExtension`. |
|
||||
| `stateTransformation ` | yes | - | One or more transformation applied to received values before updating channel. |
|
||||
| `commandTransformation` | yes | - | One or more transformation applied to channel value before sending to a remote. |
|
||||
| `mode` | no | `READWRITE` | Mode this channel is allowed to operate. `READ` means receive state, `WRITE` means send commands. |
|
||||
|
||||
Transformations need to be specified in the same format as
|
||||
Some channels have additional parameters.
|
||||
When concatenating the `baseURL` and `stateExtions` or `commandExtension` the binding checks if a proper URL part separator (`/`, `&` or `?`) is present and adds a `/` if missing.
|
||||
|
||||
### Value Transformations (`stateTransformation`, `commandTransformation`)
|
||||
|
||||
Transformations can be used if the supplied value (or the required value) is different from what openHAB internal types require.
|
||||
Here are a few examples to unwrap an incoming value via `stateTransformation` from a complex response:
|
||||
|
||||
| Received value | Tr. Service | Transformation |
|
||||
|---------------------------------------------------------------------|-------------|-------------------------------------------|
|
||||
| `{device: {status: { temperature: 23.2 }}}` | JSONPATH | `JSONPATH:$.device.status.temperature` |
|
||||
| `<device><status><temperature>23.2</temperature></status></device>` | XPath | `XPath:/device/status/temperature/text()` |
|
||||
| `THEVALUE:23.2°C` | REGEX | `REGEX::(.*?)°` |
|
||||
|
||||
Transformations can be chained by separating them with the mathematical intersection character "∩".
|
||||
Please note that the values will be discarded if one transformation fails (e.g. REGEX did not match).
|
||||
|
||||
The same mechanism works for commands (`commandTransformation`) for outgoing values.
|
||||
|
||||
### `color`
|
||||
|
||||
| parameter | optional | default | description |
|
||||
|-------------------------|----------|-------------|-------------|
|
||||
| `onValue` | yes | - | A special value that represents `ON` |
|
||||
| `offValue` | yes | - | A special value that represents `OFF` |
|
||||
| `increaseValue` | yes | - | A special value that represents `INCREASE` |
|
||||
| `decreaseValue` | yes | - | A special value that represents `DECREASE` |
|
||||
| `step` | no | 1 | The amount the brightness is increased/decreased on `INCREASE`/`DECREASE` |
|
||||
| `colorMode` | no | RGB | Mode for color values: `RGB` or `HSB` |
|
||||
|
||||
All values that are not `onValue`, `offValue`, `increaseValue`, `decreaseValue` are interpreted as color value (according to the color mode) in the format `r,g,b` or `h,s,v`.
|
||||
|
||||
### `contact`
|
||||
|
||||
| parameter | optional | default | description |
|
||||
|-------------------------|----------|-------------|-------------|
|
||||
| `openValue` | no | - | A special value that represents `OPEN` |
|
||||
| `closedValue` | no | - | A special value that represents `CLOSED` |
|
||||
|
||||
### `dimmer`
|
||||
|
||||
| parameter | optional | default | description |
|
||||
|-------------------------|----------|-------------|-------------|
|
||||
| `onValue` | yes | - | A special value that represents `ON` |
|
||||
| `offValue` | yes | - | A special value that represents `OFF` |
|
||||
| `increaseValue` | yes | - | A special value that represents `INCREASE` |
|
||||
| `decreaseValue` | yes | - | A special value that represents `DECREASE` |
|
||||
| `step` | no | 1 | The amount the brightness is increased/decreased on `INCREASE`/`DECREASE` |
|
||||
|
||||
All values that are not `onValue`, `offValue`, `increaseValue`, `decreaseValue` are interpreted as brightness 0-100% and need to be numeric only.
|
||||
|
||||
### `player`
|
||||
|
||||
| parameter | optional | default | description |
|
||||
|-------------------------|----------|-------------|-------------|
|
||||
| `play` | yes | - | A special value that represents `PLAY` |
|
||||
| `pause` | yes | - | A special value that represents `PAUSE` |
|
||||
| `next` | yes | - | A special value that represents `NEXT` |
|
||||
| `previous` | yes | - | A special value that represents `PREVIOUS` |
|
||||
| `fastforward` | yes | - | A special value that represents `FASTFORWARD` |
|
||||
| `rewind` | yes | - | A special value that represents `REWIND` |
|
||||
|
||||
### `rollershutter`
|
||||
|
||||
| parameter | optional | default | description |
|
||||
|-------------------------|----------|-------------|-------------|
|
||||
| `upValue` | yes | - | A special value that represents `UP` |
|
||||
| `downValue` | yes | - | A special value that represents `DOWN` |
|
||||
| `stopValue` | yes | - | A special value that represents `STOP` |
|
||||
| `moveValue` | yes | - | A special value that represents `MOVE` |
|
||||
|
||||
All values that are not `upValue`, `downValue`, `stopValue`, `moveValue` are interpreted as position 0-100% and need to be numeric only.
|
||||
|
||||
### `switch`
|
||||
|
||||
| parameter | optional | default | description |
|
||||
|-------------------------|----------|-------------|-------------|
|
||||
| `onValue` | no | - | A special value that represents `ON` |
|
||||
| `offValue` | no | - | A special value that represents `OFF` |
|
||||
|
||||
**Note:** Special values need to be exact matches, i.e. no leading or trailing characters and comparison is case-sensitive.
|
||||
|
||||
## URL Formatting
|
||||
|
||||
After concatenation of the `baseURL` and the `commandExtension` or the `stateExtension` (if provided) the URL is formatted using the [java.util.Formatter](http://docs.oracle.com/javase/6/docs/api/java/util/Formatter.html).
|
||||
The URL is used as format string and two parameters are added:
|
||||
|
||||
- the current date (referenced as `%1$`)
|
||||
- the transformed command (referenced as `%2$`)
|
||||
|
||||
After the parameter reference the format needs to be appended.
|
||||
See the link above for more information about the available format parameters (e.g. to use the string representation, you need to append `s` to the reference).
|
||||
When sending an OFF command on 2020-07-06, the URL
|
||||
|
||||
```
|
||||
http://www.domain.org/home/lights/23871/?status=%2$s&date=%1$tY-%1$tm-%1$td
|
||||
```
|
||||
|
||||
is transformed to
|
||||
|
||||
```
|
||||
http://www.domain.org/home/lights/23871/?status=OFF&date=2020-07-06
|
||||
```
|
17
bundles/org.openhab.binding.http/pom.xml
Normal file
17
bundles/org.openhab.binding.http/pom.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?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/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.http</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: HTTP Binding</name>
|
||||
|
||||
</project>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.http-${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-http" description="HTTP Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.http/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -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.http.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link HttpBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HttpBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "http";
|
||||
|
||||
public static final ThingTypeUID THING_TYPE_URL = new ThingTypeUID(BINDING_ID, "url");
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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.http.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
|
||||
/**
|
||||
* The {@link HttpClientProvider} defines the interface for providing {@link HttpClient} instances to thing handlers
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface HttpClientProvider {
|
||||
|
||||
/**
|
||||
* get the secure http client
|
||||
*
|
||||
* @return a HttpClient
|
||||
*/
|
||||
HttpClient getSecureClient();
|
||||
|
||||
/**
|
||||
* get the insecure http client (ignores SSL errors)
|
||||
*
|
||||
* @return q HttpClient
|
||||
*/
|
||||
HttpClient getInsecureClient();
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 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.http.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,
|
||||
HttpDynamicStateDescriptionProvider.class }, immediate = true)
|
||||
public class HttpDynamicStateDescriptionProvider implements DynamicStateDescriptionProvider {
|
||||
|
||||
private final Map<ChannelUID, StateDescription> descriptions = new ConcurrentHashMap<>();
|
||||
private final Logger logger = LoggerFactory.getLogger(HttpDynamicStateDescriptionProvider.class);
|
||||
|
||||
/**
|
||||
* 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())) {
|
||||
logger.trace("returning new stateDescription for {}", channel.getUID());
|
||||
return descriptions.get(channel.getUID());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 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.http.internal;
|
||||
|
||||
import static org.openhab.binding.http.internal.HttpBindingConstants.*;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.openhab.binding.http.internal.transform.CascadedValueTransformationImpl;
|
||||
import org.openhab.binding.http.internal.transform.NoOpValueTransformation;
|
||||
import org.openhab.binding.http.internal.transform.ValueTransformation;
|
||||
import org.openhab.binding.http.internal.transform.ValueTransformationProvider;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
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.openhab.core.transform.TransformationHelper;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link HttpHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.http", service = ThingHandlerFactory.class)
|
||||
public class HttpHandlerFactory extends BaseThingHandlerFactory
|
||||
implements ValueTransformationProvider, HttpClientProvider {
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_URL);
|
||||
private final Logger logger = LoggerFactory.getLogger(HttpHandlerFactory.class);
|
||||
|
||||
private final HttpClient secureClient;
|
||||
private final HttpClient insecureClient;
|
||||
|
||||
private final HttpDynamicStateDescriptionProvider httpDynamicStateDescriptionProvider;
|
||||
|
||||
@Activate
|
||||
public HttpHandlerFactory(@Reference HttpClientFactory httpClientFactory,
|
||||
@Reference HttpDynamicStateDescriptionProvider httpDynamicStateDescriptionProvider) {
|
||||
this.secureClient = new HttpClient(new SslContextFactory());
|
||||
this.insecureClient = new HttpClient(new SslContextFactory(true));
|
||||
try {
|
||||
this.secureClient.start();
|
||||
this.insecureClient.start();
|
||||
} catch (Exception e) {
|
||||
// catching exception is necessary due to the signature of HttpClient.start()
|
||||
logger.warn("Failed to start insecure http client: {}", e.getMessage());
|
||||
throw new IllegalStateException("Could not create insecure HttpClient");
|
||||
}
|
||||
this.httpDynamicStateDescriptionProvider = httpDynamicStateDescriptionProvider;
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
public void deactivate() {
|
||||
try {
|
||||
secureClient.stop();
|
||||
insecureClient.stop();
|
||||
} catch (Exception e) {
|
||||
// catching exception is necessary due to the signature of HttpClient.stop()
|
||||
logger.warn("Failed to stop insecure http client: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@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 (THING_TYPE_URL.equals(thingTypeUID)) {
|
||||
return new HttpThingHandler(thing, this, this, httpDynamicStateDescriptionProvider);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueTransformation getValueTransformation(@Nullable String pattern) {
|
||||
if (pattern == null) {
|
||||
return NoOpValueTransformation.getInstance();
|
||||
}
|
||||
return new CascadedValueTransformationImpl(pattern,
|
||||
name -> TransformationHelper.getTransformationService(bundleContext, name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient getSecureClient() {
|
||||
return secureClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient getInsecureClient() {
|
||||
return insecureClient;
|
||||
}
|
||||
}
|
@ -0,0 +1,367 @@
|
||||
/**
|
||||
* 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.http.internal;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
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.api.Request;
|
||||
import org.eclipse.jetty.client.util.BasicAuthentication;
|
||||
import org.eclipse.jetty.client.util.DigestAuthentication;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.http.internal.config.HttpChannelConfig;
|
||||
import org.openhab.binding.http.internal.config.HttpChannelMode;
|
||||
import org.openhab.binding.http.internal.config.HttpThingConfig;
|
||||
import org.openhab.binding.http.internal.converter.AbstractTransformingItemConverter;
|
||||
import org.openhab.binding.http.internal.converter.ColorItemConverter;
|
||||
import org.openhab.binding.http.internal.converter.DimmerItemConverter;
|
||||
import org.openhab.binding.http.internal.converter.FixedValueMappingItemConverter;
|
||||
import org.openhab.binding.http.internal.converter.GenericItemConverter;
|
||||
import org.openhab.binding.http.internal.converter.ImageItemConverter;
|
||||
import org.openhab.binding.http.internal.converter.ItemValueConverter;
|
||||
import org.openhab.binding.http.internal.converter.PlayerItemConverter;
|
||||
import org.openhab.binding.http.internal.converter.RollershutterItemConverter;
|
||||
import org.openhab.binding.http.internal.http.Content;
|
||||
import org.openhab.binding.http.internal.http.HttpAuthException;
|
||||
import org.openhab.binding.http.internal.http.HttpResponseListener;
|
||||
import org.openhab.binding.http.internal.http.RefreshingUrlCache;
|
||||
import org.openhab.binding.http.internal.transform.ValueTransformationProvider;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.StateDescription;
|
||||
import org.openhab.core.types.StateDescriptionFragmentBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link HttpThingHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HttpThingHandler extends BaseThingHandler {
|
||||
private static final Set<Character> URL_PART_DELIMITER = Set.of('/', '?', '&');
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(HttpThingHandler.class);
|
||||
private final ValueTransformationProvider valueTransformationProvider;
|
||||
private final HttpClientProvider httpClientProvider;
|
||||
private HttpClient httpClient;
|
||||
private final HttpDynamicStateDescriptionProvider httpDynamicStateDescriptionProvider;
|
||||
|
||||
private HttpThingConfig config = new HttpThingConfig();
|
||||
private final Map<String, RefreshingUrlCache> urlHandlers = new HashMap<>();
|
||||
private final Map<ChannelUID, ItemValueConverter> channels = new HashMap<>();
|
||||
private final Map<ChannelUID, String> channelUrls = new HashMap<>();
|
||||
private @Nullable Authentication authentication;
|
||||
|
||||
public HttpThingHandler(Thing thing, HttpClientProvider httpClientProvider,
|
||||
ValueTransformationProvider valueTransformationProvider,
|
||||
HttpDynamicStateDescriptionProvider httpDynamicStateDescriptionProvider) {
|
||||
super(thing);
|
||||
this.httpClientProvider = httpClientProvider;
|
||||
this.httpClient = httpClientProvider.getSecureClient();
|
||||
this.valueTransformationProvider = valueTransformationProvider;
|
||||
this.httpDynamicStateDescriptionProvider = httpDynamicStateDescriptionProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
ItemValueConverter itemValueConverter = channels.get(channelUID);
|
||||
if (itemValueConverter == null) {
|
||||
logger.warn("Cannot find channel implementation for channel {}.", channelUID);
|
||||
return;
|
||||
}
|
||||
|
||||
if (command instanceof RefreshType) {
|
||||
String stateUrl = channelUrls.get(channelUID);
|
||||
if (stateUrl != null) {
|
||||
RefreshingUrlCache refreshingUrlCache = urlHandlers.get(stateUrl);
|
||||
if (refreshingUrlCache != null) {
|
||||
try {
|
||||
refreshingUrlCache.get().ifPresent(itemValueConverter::process);
|
||||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
logger.warn("Failed processing REFRESH command for channel {}: {}", channelUID, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
itemValueConverter.send(command);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.warn("Failed to convert command '{}' to channel '{}' for sending", command, channelUID);
|
||||
} catch (IllegalStateException e) {
|
||||
logger.debug("Writing to read-only channel {} not permitted", channelUID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(HttpThingConfig.class);
|
||||
|
||||
if (config.baseURL.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Parameter baseURL must not be empty!");
|
||||
return;
|
||||
}
|
||||
authentication = null;
|
||||
if (!config.username.isEmpty()) {
|
||||
try {
|
||||
URI uri = new URI(config.baseURL);
|
||||
switch (config.authMode) {
|
||||
case BASIC:
|
||||
authentication = new BasicAuthentication(uri, Authentication.ANY_REALM, config.username,
|
||||
config.password);
|
||||
logger.debug("Basic Authentication configured for thing '{}'", thing.getUID());
|
||||
break;
|
||||
case DIGEST:
|
||||
authentication = new DigestAuthentication(uri, Authentication.ANY_REALM, config.username,
|
||||
config.password);
|
||||
logger.debug("Digest Authentication configured for thing '{}'", thing.getUID());
|
||||
break;
|
||||
default:
|
||||
logger.warn("Unknown authentication method '{}' for thing '{}'", config.authMode,
|
||||
thing.getUID());
|
||||
}
|
||||
if (authentication != null) {
|
||||
AuthenticationStore authStore = httpClient.getAuthenticationStore();
|
||||
authStore.addAuthentication(authentication);
|
||||
}
|
||||
} catch (URISyntaxException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"failed to create authentication: baseUrl is invalid");
|
||||
}
|
||||
} else {
|
||||
logger.debug("No authentication configured for thing '{}'", thing.getUID());
|
||||
}
|
||||
|
||||
if (config.ignoreSSLErrors) {
|
||||
logger.info("Using the insecure client for thing '{}'.", thing.getUID());
|
||||
httpClient = httpClientProvider.getInsecureClient();
|
||||
} else {
|
||||
logger.info("Using the secure client for thing '{}'.", thing.getUID());
|
||||
httpClient = httpClientProvider.getSecureClient();
|
||||
}
|
||||
|
||||
thing.getChannels().forEach(this::createChannel);
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
// stop update tasks
|
||||
urlHandlers.values().forEach(RefreshingUrlCache::stop);
|
||||
|
||||
// clear lists
|
||||
urlHandlers.clear();
|
||||
channels.clear();
|
||||
channelUrls.clear();
|
||||
|
||||
// remove state descriptions
|
||||
httpDynamicStateDescriptionProvider.removeDescriptionsForThing(thing.getUID());
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* create all necessary information to handle every channel
|
||||
*
|
||||
* @param channel a thing channel
|
||||
*/
|
||||
private void createChannel(Channel channel) {
|
||||
ChannelUID channelUID = channel.getUID();
|
||||
HttpChannelConfig channelConfig = channel.getConfiguration().as(HttpChannelConfig.class);
|
||||
|
||||
String stateUrl = concatenateUrlParts(config.baseURL, channelConfig.stateExtension);
|
||||
String commandUrl = channelConfig.commandExtension == null ? stateUrl
|
||||
: concatenateUrlParts(config.baseURL, channelConfig.commandExtension);
|
||||
|
||||
String acceptedItemType = channel.getAcceptedItemType();
|
||||
if (acceptedItemType == null) {
|
||||
logger.warn("Cannot determine item-type for channel '{}'", channelUID);
|
||||
return;
|
||||
}
|
||||
|
||||
ItemValueConverter itemValueConverter;
|
||||
switch (acceptedItemType) {
|
||||
case "Color":
|
||||
itemValueConverter = createItemConverter(ColorItemConverter::new, commandUrl, channelUID,
|
||||
channelConfig);
|
||||
break;
|
||||
case "DateTime":
|
||||
itemValueConverter = createGenericItemConverter(commandUrl, channelUID, channelConfig,
|
||||
DateTimeType::new);
|
||||
break;
|
||||
case "Dimmer":
|
||||
itemValueConverter = createItemConverter(DimmerItemConverter::new, commandUrl, channelUID,
|
||||
channelConfig);
|
||||
break;
|
||||
case "Contact":
|
||||
case "Switch":
|
||||
itemValueConverter = createItemConverter(FixedValueMappingItemConverter::new, commandUrl, channelUID,
|
||||
channelConfig);
|
||||
break;
|
||||
case "Image":
|
||||
itemValueConverter = new ImageItemConverter(state -> updateState(channelUID, state));
|
||||
break;
|
||||
case "Location":
|
||||
itemValueConverter = createGenericItemConverter(commandUrl, channelUID, channelConfig, PointType::new);
|
||||
break;
|
||||
case "Number":
|
||||
itemValueConverter = createGenericItemConverter(commandUrl, channelUID, channelConfig,
|
||||
DecimalType::new);
|
||||
break;
|
||||
case "Player":
|
||||
itemValueConverter = createItemConverter(PlayerItemConverter::new, commandUrl, channelUID,
|
||||
channelConfig);
|
||||
break;
|
||||
case "Rollershutter":
|
||||
itemValueConverter = createItemConverter(RollershutterItemConverter::new, commandUrl, channelUID,
|
||||
channelConfig);
|
||||
break;
|
||||
case "String":
|
||||
itemValueConverter = createGenericItemConverter(commandUrl, channelUID, channelConfig, StringType::new);
|
||||
break;
|
||||
default:
|
||||
logger.warn("Unsupported item-type '{}'", channel.getAcceptedItemType());
|
||||
return;
|
||||
}
|
||||
|
||||
channels.put(channelUID, itemValueConverter);
|
||||
if (channelConfig.mode != HttpChannelMode.WRITEONLY) {
|
||||
channelUrls.put(channelUID, stateUrl);
|
||||
urlHandlers.computeIfAbsent(stateUrl, url -> new RefreshingUrlCache(scheduler, httpClient, url, config))
|
||||
.addConsumer(itemValueConverter::process);
|
||||
}
|
||||
|
||||
StateDescription stateDescription = StateDescriptionFragmentBuilder.create()
|
||||
.withReadOnly(channelConfig.mode == HttpChannelMode.READONLY).build().toStateDescription();
|
||||
if (stateDescription != null) {
|
||||
// if the state description is not available, we don'tneed to add it
|
||||
httpDynamicStateDescriptionProvider.setDescription(channelUID, stateDescription);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendHttpValue(String commandUrl, String command) {
|
||||
sendHttpValue(commandUrl, command, false);
|
||||
}
|
||||
|
||||
private void sendHttpValue(String commandUrl, String command, boolean isRetry) {
|
||||
try {
|
||||
// format URL
|
||||
URI finalUrl = new URI(String.format(commandUrl, new Date(), command));
|
||||
|
||||
// build request
|
||||
Request request = httpClient.newRequest(finalUrl).timeout(config.timeout, TimeUnit.MILLISECONDS)
|
||||
.method(config.commandMethod);
|
||||
if (config.commandMethod != HttpMethod.GET) {
|
||||
final String contentType = config.contentType;
|
||||
if (contentType != null) {
|
||||
request.content(new StringContentProvider(command), contentType);
|
||||
} else {
|
||||
request.content(new StringContentProvider(command));
|
||||
}
|
||||
}
|
||||
|
||||
config.headers.forEach(header -> {
|
||||
String[] keyValuePair = header.split("=", 2);
|
||||
if (keyValuePair.length == 2) {
|
||||
request.header(keyValuePair[0], keyValuePair[1]);
|
||||
} else {
|
||||
logger.warn("Splitting header '{}' failed. No '=' was found. Ignoring", header);
|
||||
}
|
||||
});
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Sending to '{}': {}", finalUrl, Util.requestToLogString(request));
|
||||
}
|
||||
|
||||
CompletableFuture<@Nullable Content> f = new CompletableFuture<>();
|
||||
f.exceptionally(e -> {
|
||||
if (e instanceof HttpAuthException) {
|
||||
if (isRetry) {
|
||||
logger.warn("Retry after authentication failure failed again for '{}', failing here", finalUrl);
|
||||
} else {
|
||||
AuthenticationStore authStore = httpClient.getAuthenticationStore();
|
||||
Authentication.Result authResult = authStore.findAuthenticationResult(finalUrl);
|
||||
if (authResult != null) {
|
||||
authStore.removeAuthenticationResult(authResult);
|
||||
logger.debug("Cleared authentication result for '{}', retrying immediately", finalUrl);
|
||||
sendHttpValue(commandUrl, command, true);
|
||||
} else {
|
||||
logger.warn("Could not find authentication result for '{}', failing here", finalUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
request.send(new HttpResponseListener(f));
|
||||
} catch (IllegalArgumentException | URISyntaxException e) {
|
||||
logger.warn("Creating request for '{}' failed: {}", commandUrl, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String concatenateUrlParts(String baseUrl, @Nullable String extension) {
|
||||
if (extension != null && !extension.isEmpty()) {
|
||||
if (!URL_PART_DELIMITER.contains(baseUrl.charAt(baseUrl.length() - 1))
|
||||
&& !URL_PART_DELIMITER.contains(extension.charAt(0))) {
|
||||
return baseUrl + "/" + extension;
|
||||
} else {
|
||||
return baseUrl + extension;
|
||||
}
|
||||
} else {
|
||||
return baseUrl;
|
||||
}
|
||||
}
|
||||
|
||||
private ItemValueConverter createItemConverter(AbstractTransformingItemConverter.Factory factory, String commandUrl,
|
||||
ChannelUID channelUID, HttpChannelConfig channelConfig) {
|
||||
return factory.create(state -> updateState(channelUID, state), command -> postCommand(channelUID, command),
|
||||
command -> sendHttpValue(commandUrl, command),
|
||||
valueTransformationProvider.getValueTransformation(channelConfig.stateTransformation),
|
||||
valueTransformationProvider.getValueTransformation(channelConfig.commandTransformation), channelConfig);
|
||||
}
|
||||
|
||||
private ItemValueConverter createGenericItemConverter(String commandUrl, ChannelUID channelUID,
|
||||
HttpChannelConfig channelConfig, Function<String, State> toState) {
|
||||
AbstractTransformingItemConverter.Factory factory = (state, command, value, stateTrans, commandTrans,
|
||||
config) -> new GenericItemConverter(toState, state, command, value, stateTrans, commandTrans, config);
|
||||
return createItemConverter(factory, commandUrl, channelUID, channelConfig);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.api.ContentProvider;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
|
||||
/**
|
||||
* The {@link Util} is a utility class
|
||||
* channels
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Util {
|
||||
|
||||
public static String requestToLogString(Request request) {
|
||||
ContentProvider contentProvider = request.getContent();
|
||||
String contentString = contentProvider == null ? "null"
|
||||
: StreamSupport.stream(contentProvider.spliterator(), false)
|
||||
.map(b -> StandardCharsets.UTF_8.decode(b).toString()).collect(Collectors.joining(", "));
|
||||
String logString = "Method = {" + request.getMethod() + "}, Headers = {"
|
||||
+ request.getHeaders().stream().map(HttpField::toString).collect(Collectors.joining(", "))
|
||||
+ "}, Content = {" + contentString + "}";
|
||||
|
||||
return logString;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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.http.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link HttpAuthMode} enum defines the method used for authentication.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum HttpAuthMode {
|
||||
BASIC,
|
||||
DIGEST
|
||||
}
|
@ -0,0 +1,137 @@
|
||||
/**
|
||||
* 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.http.internal.config;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.http.internal.converter.ColorItemConverter;
|
||||
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||
import org.openhab.core.library.types.NextPreviousType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.PlayPauseType;
|
||||
import org.openhab.core.library.types.RewindFastforwardType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link HttpChannelConfig} class contains fields mapping channel configuration parameters.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HttpChannelConfig {
|
||||
private final Map<String, State> stringStateMap = new HashMap<>();
|
||||
private final Map<Command, @Nullable String> commandStringMap = new HashMap<>();
|
||||
private boolean initialized = false;
|
||||
|
||||
public @Nullable String stateExtension;
|
||||
public @Nullable String commandExtension;
|
||||
public @Nullable String stateTransformation;
|
||||
public @Nullable String commandTransformation;
|
||||
|
||||
public HttpChannelMode mode = HttpChannelMode.READWRITE;
|
||||
|
||||
// switch, dimmer, color
|
||||
public @Nullable String onValue;
|
||||
public @Nullable String offValue;
|
||||
|
||||
// dimmer, color
|
||||
public BigDecimal step = BigDecimal.ONE;
|
||||
public @Nullable String increaseValue;
|
||||
public @Nullable String decreaseValue;
|
||||
|
||||
// color
|
||||
public ColorItemConverter.ColorMode colorMode = ColorItemConverter.ColorMode.RGB;
|
||||
|
||||
// contact
|
||||
public @Nullable String openValue;
|
||||
public @Nullable String closedValue;
|
||||
|
||||
// rollershutter
|
||||
public @Nullable String upValue;
|
||||
public @Nullable String downValue;
|
||||
public @Nullable String stopValue;
|
||||
public @Nullable String moveValue;
|
||||
|
||||
// player
|
||||
public @Nullable String playValue;
|
||||
public @Nullable String pauseValue;
|
||||
public @Nullable String nextValue;
|
||||
public @Nullable String previousValue;
|
||||
public @Nullable String rewindValue;
|
||||
public @Nullable String fastforwardValue;
|
||||
|
||||
/**
|
||||
* maps a command to a user-defined string
|
||||
*
|
||||
* @param command the command to map
|
||||
* @return a string or null if no mapping found
|
||||
*/
|
||||
public @Nullable String commandToFixedValue(Command command) {
|
||||
if (!initialized) {
|
||||
createMaps();
|
||||
}
|
||||
|
||||
return commandStringMap.get(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* maps a user-defined string to a state
|
||||
*
|
||||
* @param string the string to map
|
||||
* @return the state or null if no mapping found
|
||||
*/
|
||||
public @Nullable State fixedValueToState(String string) {
|
||||
if (!initialized) {
|
||||
createMaps();
|
||||
}
|
||||
|
||||
return stringStateMap.get(string);
|
||||
}
|
||||
|
||||
private void createMaps() {
|
||||
addToMaps(this.onValue, OnOffType.ON);
|
||||
addToMaps(this.offValue, OnOffType.OFF);
|
||||
addToMaps(this.openValue, OpenClosedType.OPEN);
|
||||
addToMaps(this.closedValue, OpenClosedType.CLOSED);
|
||||
addToMaps(this.upValue, UpDownType.UP);
|
||||
addToMaps(this.downValue, UpDownType.DOWN);
|
||||
|
||||
commandStringMap.put(IncreaseDecreaseType.INCREASE, increaseValue);
|
||||
commandStringMap.put(IncreaseDecreaseType.DECREASE, decreaseValue);
|
||||
commandStringMap.put(StopMoveType.STOP, stopValue);
|
||||
commandStringMap.put(StopMoveType.MOVE, moveValue);
|
||||
commandStringMap.put(PlayPauseType.PLAY, playValue);
|
||||
commandStringMap.put(PlayPauseType.PAUSE, pauseValue);
|
||||
commandStringMap.put(NextPreviousType.NEXT, nextValue);
|
||||
commandStringMap.put(NextPreviousType.PREVIOUS, previousValue);
|
||||
commandStringMap.put(RewindFastforwardType.REWIND, rewindValue);
|
||||
commandStringMap.put(RewindFastforwardType.FASTFORWARD, fastforwardValue);
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
private void addToMaps(@Nullable String value, State state) {
|
||||
if (value != null) {
|
||||
commandStringMap.put((Command) state, value);
|
||||
stringStateMap.put(value, state);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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.http.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link HttpChannelMode} enum defines control modes for channels
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum HttpChannelMode {
|
||||
READONLY,
|
||||
READWRITE,
|
||||
WRITEONLY
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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.http.internal.config;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
|
||||
/**
|
||||
* The {@link HttpThingConfig} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HttpThingConfig {
|
||||
public String baseURL = "";
|
||||
public int refresh = 30;
|
||||
public int timeout = 3000;
|
||||
|
||||
public String username = "";
|
||||
public String password = "";
|
||||
public HttpAuthMode authMode = HttpAuthMode.BASIC;
|
||||
|
||||
public HttpMethod commandMethod = HttpMethod.GET;
|
||||
|
||||
public @Nullable String encoding = null;
|
||||
public @Nullable String contentType = null;
|
||||
|
||||
public boolean ignoreSSLErrors = false;
|
||||
|
||||
public List<String> headers = Collections.emptyList();
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 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.http.internal.converter;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.http.internal.config.HttpChannelConfig;
|
||||
import org.openhab.binding.http.internal.config.HttpChannelMode;
|
||||
import org.openhab.binding.http.internal.http.Content;
|
||||
import org.openhab.binding.http.internal.transform.ValueTransformation;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link AbstractTransformingItemConverter} is a base class for an item converter with transformations
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class AbstractTransformingItemConverter implements ItemValueConverter {
|
||||
private final Consumer<State> updateState;
|
||||
private final Consumer<Command> postCommand;
|
||||
private final @Nullable Consumer<String> sendHttpValue;
|
||||
private final ValueTransformation stateTransformations;
|
||||
private final ValueTransformation commandTransformations;
|
||||
|
||||
protected HttpChannelConfig channelConfig;
|
||||
|
||||
public AbstractTransformingItemConverter(Consumer<State> updateState, Consumer<Command> postCommand,
|
||||
@Nullable Consumer<String> sendHttpValue, ValueTransformation stateTransformations,
|
||||
ValueTransformation commandTransformations, HttpChannelConfig channelConfig) {
|
||||
this.updateState = updateState;
|
||||
this.postCommand = postCommand;
|
||||
this.sendHttpValue = sendHttpValue;
|
||||
this.stateTransformations = stateTransformations;
|
||||
this.commandTransformations = commandTransformations;
|
||||
this.channelConfig = channelConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(Content content) {
|
||||
if (channelConfig.mode != HttpChannelMode.WRITEONLY) {
|
||||
stateTransformations.apply(content.getAsString()).ifPresent(transformedValue -> {
|
||||
Command command = toCommand(transformedValue);
|
||||
if (command != null) {
|
||||
postCommand.accept(command);
|
||||
} else {
|
||||
updateState.accept(toState(transformedValue));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw new IllegalStateException("Write-only channel");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Command command) {
|
||||
Consumer<String> sendHttpValue = this.sendHttpValue;
|
||||
if (sendHttpValue != null && channelConfig.mode != HttpChannelMode.READONLY) {
|
||||
commandTransformations.apply(toString(command)).ifPresent(sendHttpValue);
|
||||
} else {
|
||||
throw new IllegalStateException("Read-only channel");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check if this converter received a value that needs to be sent as command
|
||||
*
|
||||
* @param value the value
|
||||
* @return the command or null
|
||||
*/
|
||||
protected abstract @Nullable Command toCommand(String value);
|
||||
|
||||
/**
|
||||
* convert the received value to a state
|
||||
*
|
||||
* @param value the value
|
||||
* @return the state that represents the value of UNDEF if conversion failed
|
||||
*/
|
||||
protected abstract State toState(String value);
|
||||
|
||||
/**
|
||||
* convert a command to a string
|
||||
*
|
||||
* @param command the command
|
||||
* @return the string representation of the command
|
||||
*/
|
||||
protected abstract String toString(Command command);
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Factory {
|
||||
ItemValueConverter create(Consumer<State> updateState, Consumer<Command> postCommand,
|
||||
@Nullable Consumer<String> sendHttpValue, ValueTransformation stateTransformations,
|
||||
ValueTransformation commandTransformations, HttpChannelConfig channelConfig);
|
||||
}
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
/**
|
||||
* 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.http.internal.converter;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.http.internal.config.HttpChannelConfig;
|
||||
import org.openhab.binding.http.internal.transform.ValueTransformation;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link ColorItemConverter} implements {@link org.openhab.core.library.items.ColorItem} conversions
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class ColorItemConverter extends AbstractTransformingItemConverter {
|
||||
private static final BigDecimal BYTE_FACTOR = BigDecimal.valueOf(2.55);
|
||||
private static final BigDecimal HUNDRED = BigDecimal.valueOf(100);
|
||||
private static final Pattern TRIPLE_MATCHER = Pattern.compile("(\\d+),(\\d+),(\\d+)");
|
||||
|
||||
private State state = UnDefType.UNDEF;
|
||||
|
||||
public ColorItemConverter(Consumer<State> updateState, Consumer<Command> postCommand,
|
||||
@Nullable Consumer<String> sendHttpValue, ValueTransformation stateTransformations,
|
||||
ValueTransformation commandTransformations, HttpChannelConfig channelConfig) {
|
||||
super(updateState, postCommand, sendHttpValue, stateTransformations, commandTransformations, channelConfig);
|
||||
this.channelConfig = channelConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Command toCommand(String value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Command command) {
|
||||
String string = channelConfig.commandToFixedValue(command);
|
||||
if (string != null) {
|
||||
return string;
|
||||
}
|
||||
|
||||
if (command instanceof HSBType) {
|
||||
HSBType newState = (HSBType) command;
|
||||
state = newState;
|
||||
return hsbToString(newState);
|
||||
} else if (command instanceof PercentType && state instanceof HSBType) {
|
||||
HSBType newState = new HSBType(((HSBType) state).getBrightness(), ((HSBType) state).getSaturation(),
|
||||
(PercentType) command);
|
||||
state = newState;
|
||||
return hsbToString(newState);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Command type '" + command.toString() + "' not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public State toState(String string) {
|
||||
State newState = UnDefType.UNDEF;
|
||||
if (string.equals(channelConfig.onValue)) {
|
||||
if (state instanceof HSBType) {
|
||||
newState = new HSBType(((HSBType) state).getHue(), ((HSBType) state).getSaturation(),
|
||||
PercentType.HUNDRED);
|
||||
} else {
|
||||
newState = HSBType.WHITE;
|
||||
}
|
||||
} else if (string.equals(channelConfig.offValue)) {
|
||||
if (state instanceof HSBType) {
|
||||
newState = new HSBType(((HSBType) state).getHue(), ((HSBType) state).getSaturation(), PercentType.ZERO);
|
||||
} else {
|
||||
newState = HSBType.BLACK;
|
||||
}
|
||||
} else if (string.equals(channelConfig.increaseValue) && state instanceof HSBType) {
|
||||
BigDecimal newBrightness = ((HSBType) state).getBrightness().toBigDecimal().add(channelConfig.step);
|
||||
if (HUNDRED.compareTo(newBrightness) < 0) {
|
||||
newBrightness = HUNDRED;
|
||||
}
|
||||
newState = new HSBType(((HSBType) state).getHue(), ((HSBType) state).getSaturation(),
|
||||
new PercentType(newBrightness));
|
||||
} else if (string.equals(channelConfig.decreaseValue) && state instanceof HSBType) {
|
||||
BigDecimal newBrightness = ((HSBType) state).getBrightness().toBigDecimal().subtract(channelConfig.step);
|
||||
if (BigDecimal.ZERO.compareTo(newBrightness) > 0) {
|
||||
newBrightness = BigDecimal.ZERO;
|
||||
}
|
||||
newState = new HSBType(((HSBType) state).getHue(), ((HSBType) state).getSaturation(),
|
||||
new PercentType(newBrightness));
|
||||
} else {
|
||||
Matcher matcher = TRIPLE_MATCHER.matcher(string);
|
||||
if (matcher.matches()) {
|
||||
switch (channelConfig.colorMode) {
|
||||
case RGB:
|
||||
int r = Integer.parseInt(matcher.group(0));
|
||||
int g = Integer.parseInt(matcher.group(1));
|
||||
int b = Integer.parseInt(matcher.group(2));
|
||||
newState = HSBType.fromRGB(r, g, b);
|
||||
break;
|
||||
case HSB:
|
||||
newState = new HSBType(string);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state = newState;
|
||||
return newState;
|
||||
}
|
||||
|
||||
private String hsbToString(HSBType state) {
|
||||
switch (channelConfig.colorMode) {
|
||||
case RGB:
|
||||
PercentType[] rgb = state.toRGB();
|
||||
return String.format("%1$d,%2$d,%3$d", rgb[0].toBigDecimal().multiply(BYTE_FACTOR).intValue(),
|
||||
rgb[1].toBigDecimal().multiply(BYTE_FACTOR).intValue(),
|
||||
rgb[2].toBigDecimal().multiply(BYTE_FACTOR).intValue());
|
||||
case HSB:
|
||||
return state.toString();
|
||||
}
|
||||
throw new IllegalStateException("Invalid colorMode setting");
|
||||
}
|
||||
|
||||
public enum ColorMode {
|
||||
RGB,
|
||||
HSB
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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.http.internal.converter;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.http.internal.config.HttpChannelConfig;
|
||||
import org.openhab.binding.http.internal.transform.ValueTransformation;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link DimmerItemConverter} implements {@link org.openhab.core.library.items.DimmerItem} conversions
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class DimmerItemConverter extends AbstractTransformingItemConverter {
|
||||
private static final BigDecimal HUNDRED = BigDecimal.valueOf(100);
|
||||
|
||||
private State state = UnDefType.UNDEF;
|
||||
|
||||
public DimmerItemConverter(Consumer<State> updateState, Consumer<Command> postCommand,
|
||||
@Nullable Consumer<String> sendHttpValue, ValueTransformation stateTransformations,
|
||||
ValueTransformation commandTransformations, HttpChannelConfig channelConfig) {
|
||||
super(updateState, postCommand, sendHttpValue, stateTransformations, commandTransformations, channelConfig);
|
||||
this.channelConfig = channelConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Command toCommand(String value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Command command) {
|
||||
String string = channelConfig.commandToFixedValue(command);
|
||||
if (string != null) {
|
||||
return string;
|
||||
}
|
||||
|
||||
if (command instanceof PercentType) {
|
||||
return ((PercentType) command).toString();
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Command type '" + command.toString() + "' not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public State toState(String string) {
|
||||
State newState = UnDefType.UNDEF;
|
||||
|
||||
if (string.equals(channelConfig.onValue)) {
|
||||
newState = PercentType.HUNDRED;
|
||||
} else if (string.equals(channelConfig.offValue)) {
|
||||
newState = PercentType.ZERO;
|
||||
} else if (string.equals(channelConfig.increaseValue) && state instanceof PercentType) {
|
||||
BigDecimal newBrightness = ((PercentType) state).toBigDecimal().add(channelConfig.step);
|
||||
if (HUNDRED.compareTo(newBrightness) < 0) {
|
||||
newBrightness = HUNDRED;
|
||||
}
|
||||
newState = new PercentType(newBrightness);
|
||||
} else if (string.equals(channelConfig.decreaseValue) && state instanceof PercentType) {
|
||||
BigDecimal newBrightness = ((PercentType) state).toBigDecimal().subtract(channelConfig.step);
|
||||
if (BigDecimal.ZERO.compareTo(newBrightness) > 0) {
|
||||
newBrightness = BigDecimal.ZERO;
|
||||
}
|
||||
newState = new PercentType(newBrightness);
|
||||
} else {
|
||||
try {
|
||||
BigDecimal value = new BigDecimal(string);
|
||||
if (value.compareTo(PercentType.HUNDRED.toBigDecimal()) > 0) {
|
||||
value = PercentType.HUNDRED.toBigDecimal();
|
||||
}
|
||||
if (value.compareTo(PercentType.ZERO.toBigDecimal()) < 0) {
|
||||
value = PercentType.ZERO.toBigDecimal();
|
||||
}
|
||||
newState = new PercentType(value);
|
||||
} catch (NumberFormatException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
state = newState;
|
||||
return newState;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 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.http.internal.converter;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.http.internal.config.HttpChannelConfig;
|
||||
import org.openhab.binding.http.internal.transform.ValueTransformation;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link FixedValueMappingItemConverter} implements mapping conversions for different item-types
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class FixedValueMappingItemConverter extends AbstractTransformingItemConverter {
|
||||
|
||||
public FixedValueMappingItemConverter(Consumer<State> updateState, Consumer<Command> postCommand,
|
||||
@Nullable Consumer<String> sendHttpValue, ValueTransformation stateTransformations,
|
||||
ValueTransformation commandTransformations, HttpChannelConfig channelConfig) {
|
||||
super(updateState, postCommand, sendHttpValue, stateTransformations, commandTransformations, channelConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Command toCommand(String value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Command command) {
|
||||
String value = channelConfig.commandToFixedValue(command);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
"Command type '" + command.toString() + "' not supported or mapping not defined.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public State toState(String string) {
|
||||
State state = channelConfig.fixedValueToState(string);
|
||||
|
||||
return state != null ? state : UnDefType.UNDEF;
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.converter;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.http.internal.config.HttpChannelConfig;
|
||||
import org.openhab.binding.http.internal.transform.ValueTransformation;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link GenericItemConverter} implements simple conversions for different item types
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GenericItemConverter extends AbstractTransformingItemConverter {
|
||||
private final Function<String, State> toState;
|
||||
|
||||
public GenericItemConverter(Function<String, State> toState, Consumer<State> updateState,
|
||||
Consumer<Command> postCommand, @Nullable Consumer<String> sendHttpValue,
|
||||
ValueTransformation stateTransformations, ValueTransformation commandTransformations,
|
||||
HttpChannelConfig channelConfig) {
|
||||
super(updateState, postCommand, sendHttpValue, stateTransformations, commandTransformations, channelConfig);
|
||||
this.toState = toState;
|
||||
}
|
||||
|
||||
protected State toState(String value) {
|
||||
try {
|
||||
return toState.apply(value);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Command toCommand(String value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String toString(Command command) {
|
||||
return command.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.converter;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.http.internal.http.Content;
|
||||
import org.openhab.core.library.types.RawType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link ImageItemConverter} implements {@link org.openhab.core.library.items.ImageItem} conversions
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class ImageItemConverter implements ItemValueConverter {
|
||||
private final Consumer<State> updateState;
|
||||
|
||||
public ImageItemConverter(Consumer<State> updateState) {
|
||||
this.updateState = updateState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(Content content) {
|
||||
String mediaType = content.getMediaType();
|
||||
updateState.accept(
|
||||
new RawType(content.getRawContent(), mediaType != null ? mediaType : RawType.DEFAULT_MIME_TYPE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Command command) {
|
||||
throw new IllegalStateException("Read-only channel");
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.converter;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.http.internal.http.Content;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* The {@link ItemValueConverter} defines the interface for converting received content to item state and converting
|
||||
* comannds to sending value
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ItemValueConverter {
|
||||
|
||||
/**
|
||||
* called to process a given content for this channel
|
||||
*
|
||||
* @param content content of the HTTP request
|
||||
*/
|
||||
void process(Content content);
|
||||
|
||||
/**
|
||||
* called to send a command to this channel
|
||||
*
|
||||
* @param command
|
||||
*/
|
||||
void send(Command command);
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* 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.http.internal.converter;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.http.internal.config.HttpChannelConfig;
|
||||
import org.openhab.binding.http.internal.transform.ValueTransformation;
|
||||
import org.openhab.core.library.types.NextPreviousType;
|
||||
import org.openhab.core.library.types.PlayPauseType;
|
||||
import org.openhab.core.library.types.RewindFastforwardType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link PlayerItemConverter} implements {@link org.openhab.core.library.items.RollershutterItem}
|
||||
* conversions
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class PlayerItemConverter extends AbstractTransformingItemConverter {
|
||||
private final HttpChannelConfig channelConfig;
|
||||
|
||||
public PlayerItemConverter(Consumer<State> updateState, Consumer<Command> postCommand,
|
||||
@Nullable Consumer<String> sendHttpValue, ValueTransformation stateTransformations,
|
||||
ValueTransformation commandTransformations, HttpChannelConfig channelConfig) {
|
||||
super(updateState, postCommand, sendHttpValue, stateTransformations, commandTransformations, channelConfig);
|
||||
this.channelConfig = channelConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Command command) {
|
||||
String string = channelConfig.commandToFixedValue(command);
|
||||
if (string != null) {
|
||||
return string;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Command type '" + command.toString() + "' not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Command toCommand(String string) {
|
||||
if (string.equals(channelConfig.playValue)) {
|
||||
return PlayPauseType.PLAY;
|
||||
} else if (string.equals(channelConfig.pauseValue)) {
|
||||
return PlayPauseType.PAUSE;
|
||||
} else if (string.equals(channelConfig.nextValue)) {
|
||||
return NextPreviousType.NEXT;
|
||||
} else if (string.equals(channelConfig.previousValue)) {
|
||||
return NextPreviousType.PREVIOUS;
|
||||
} else if (string.equals(channelConfig.rewindValue)) {
|
||||
return RewindFastforwardType.REWIND;
|
||||
} else if (string.equals(channelConfig.fastforwardValue)) {
|
||||
return RewindFastforwardType.FASTFORWARD;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public State toState(String string) {
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
@ -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.http.internal.converter;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.http.internal.config.HttpChannelConfig;
|
||||
import org.openhab.binding.http.internal.transform.ValueTransformation;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link RollershutterItemConverter} implements {@link org.openhab.core.library.items.RollershutterItem}
|
||||
* conversions
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class RollershutterItemConverter extends AbstractTransformingItemConverter {
|
||||
private final HttpChannelConfig channelConfig;
|
||||
|
||||
public RollershutterItemConverter(Consumer<State> updateState, Consumer<Command> postCommand,
|
||||
@Nullable Consumer<String> sendHttpValue, ValueTransformation stateTransformations,
|
||||
ValueTransformation commandTransformations, HttpChannelConfig channelConfig) {
|
||||
super(updateState, postCommand, sendHttpValue, stateTransformations, commandTransformations, channelConfig);
|
||||
this.channelConfig = channelConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Command command) {
|
||||
String string = channelConfig.commandToFixedValue(command);
|
||||
if (string != null) {
|
||||
return string;
|
||||
}
|
||||
|
||||
if (command instanceof PercentType) {
|
||||
final String downValue = channelConfig.downValue;
|
||||
final String upValue = channelConfig.upValue;
|
||||
if (command.equals(PercentType.HUNDRED) && downValue != null) {
|
||||
return downValue;
|
||||
} else if (command.equals(PercentType.ZERO) && upValue != null) {
|
||||
return upValue;
|
||||
} else {
|
||||
return ((PercentType) command).toString();
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Command type '" + command.toString() + "' not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Command toCommand(String string) {
|
||||
if (string.equals(channelConfig.upValue)) {
|
||||
return UpDownType.UP;
|
||||
} else if (string.equals(channelConfig.downValue)) {
|
||||
return UpDownType.DOWN;
|
||||
} else if (string.equals(channelConfig.moveValue)) {
|
||||
return StopMoveType.MOVE;
|
||||
} else if (string.equals(channelConfig.stopValue)) {
|
||||
return StopMoveType.STOP;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public State toState(String string) {
|
||||
try {
|
||||
BigDecimal value = new BigDecimal(string);
|
||||
if (value.compareTo(PercentType.HUNDRED.toBigDecimal()) > 0) {
|
||||
return PercentType.HUNDRED;
|
||||
}
|
||||
if (value.compareTo(PercentType.ZERO.toBigDecimal()) < 0) {
|
||||
return PercentType.ZERO;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.http;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link Content} defines the pre-processed response
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Content {
|
||||
private final byte[] rawContent;
|
||||
private final Charset encoding;
|
||||
private final @Nullable String mediaType;
|
||||
|
||||
public Content(byte[] rawContent, String encoding, @Nullable String mediaType) {
|
||||
this.rawContent = rawContent;
|
||||
this.mediaType = mediaType;
|
||||
|
||||
Charset finalEncoding = StandardCharsets.UTF_8;
|
||||
try {
|
||||
finalEncoding = Charset.forName(encoding);
|
||||
} catch (IllegalArgumentException e) {
|
||||
}
|
||||
this.encoding = finalEncoding;
|
||||
}
|
||||
|
||||
public byte[] getRawContent() {
|
||||
return rawContent;
|
||||
}
|
||||
|
||||
public String getAsString() {
|
||||
return new String(rawContent, encoding);
|
||||
}
|
||||
|
||||
public @Nullable String getMediaType() {
|
||||
return mediaType;
|
||||
}
|
||||
}
|
@ -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.http.internal.http;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link HttpAuthException} is an exception after authorization errors
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HttpAuthException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public HttpAuthException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public HttpAuthException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 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.http.internal.http;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link HttpResponseListener} is responsible for processing the result of a HTTP request
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HttpResponseListener extends BufferingResponseListener {
|
||||
private final Logger logger = LoggerFactory.getLogger(HttpResponseListener.class);
|
||||
private final CompletableFuture<@Nullable Content> future;
|
||||
private final String fallbackEncoding;
|
||||
|
||||
public HttpResponseListener(CompletableFuture<@Nullable Content> future) {
|
||||
this(future, null);
|
||||
}
|
||||
|
||||
public HttpResponseListener(CompletableFuture<@Nullable Content> future, @Nullable String fallbackEncoding) {
|
||||
this.future = future;
|
||||
this.fallbackEncoding = fallbackEncoding != null ? fallbackEncoding : StandardCharsets.UTF_8.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(@NonNullByDefault({}) Result result) {
|
||||
Response response = result.getResponse();
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Received from '{}': {}", result.getRequest().getURI(), responseToLogString(response));
|
||||
}
|
||||
Request request = result.getRequest();
|
||||
if (result.isFailed()) {
|
||||
logger.warn("Requesting '{}' (method='{}', content='{}') failed: {}", request.getURI(), request.getMethod(),
|
||||
request.getContent(), result.getFailure().getMessage());
|
||||
future.complete(null);
|
||||
} else {
|
||||
switch (response.getStatus()) {
|
||||
case HttpStatus.OK_200:
|
||||
byte[] content = getContent();
|
||||
String encoding = getEncoding();
|
||||
if (content != null) {
|
||||
future.complete(
|
||||
new Content(content, encoding == null ? fallbackEncoding : encoding, getMediaType()));
|
||||
} else {
|
||||
future.complete(null);
|
||||
}
|
||||
break;
|
||||
case HttpStatus.UNAUTHORIZED_401:
|
||||
logger.debug("Requesting '{}' (method='{}', content='{}') failed: Authorization error",
|
||||
request.getURI(), request.getMethod(), request.getContent());
|
||||
future.completeExceptionally(new HttpAuthException());
|
||||
break;
|
||||
default:
|
||||
logger.warn("Requesting '{}' (method='{}', content='{}') failed: {} {}", request.getURI(),
|
||||
request.getMethod(), request.getContent(), response.getStatus(), response.getReason());
|
||||
future.completeExceptionally(new IllegalStateException("Response - Code" + response.getStatus()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String responseToLogString(Response response) {
|
||||
String logString = "Code = {" + response.getStatus() + "}, Headers = {"
|
||||
+ response.getHeaders().stream().map(HttpField::toString).collect(Collectors.joining(", "))
|
||||
+ "}, Content = {" + getContentAsString() + "}";
|
||||
return logString;
|
||||
}
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
/**
|
||||
* 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.http.internal.http;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
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.api.Request;
|
||||
import org.openhab.binding.http.internal.Util;
|
||||
import org.openhab.binding.http.internal.config.HttpThingConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link RefreshingUrlCache} is responsible for requesting from a single URL and passing the content to the
|
||||
* channels
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RefreshingUrlCache {
|
||||
private final Logger logger = LoggerFactory.getLogger(RefreshingUrlCache.class);
|
||||
|
||||
private final String url;
|
||||
private final HttpClient httpClient;
|
||||
private final int timeout;
|
||||
private final @Nullable String fallbackEncoding;
|
||||
private final Set<Consumer<Content>> consumers = ConcurrentHashMap.newKeySet();
|
||||
private final List<String> headers;
|
||||
|
||||
private final ScheduledFuture<?> future;
|
||||
private @Nullable Content lastContent;
|
||||
|
||||
public RefreshingUrlCache(ScheduledExecutorService executor, HttpClient httpClient, String url,
|
||||
HttpThingConfig thingConfig) {
|
||||
this.httpClient = httpClient;
|
||||
this.url = url;
|
||||
this.timeout = thingConfig.timeout;
|
||||
this.headers = thingConfig.headers;
|
||||
fallbackEncoding = thingConfig.encoding;
|
||||
|
||||
future = executor.scheduleWithFixedDelay(this::refresh, 0, thingConfig.refresh, TimeUnit.SECONDS);
|
||||
logger.trace("Started refresh task for URL '{}' with interval {}s", url, thingConfig.refresh);
|
||||
}
|
||||
|
||||
private void refresh() {
|
||||
refresh(false);
|
||||
}
|
||||
|
||||
private void refresh(boolean isRetry) {
|
||||
if (consumers.isEmpty()) {
|
||||
// do not refresh if we don't have listeners
|
||||
return;
|
||||
}
|
||||
|
||||
// format URL
|
||||
try {
|
||||
URI finalUrl = new URI(String.format(this.url, new Date()));
|
||||
|
||||
logger.trace("Requesting refresh (retry={}) from '{}' with timeout {}ms", isRetry, finalUrl, timeout);
|
||||
Request request = httpClient.newRequest(finalUrl).timeout(timeout, TimeUnit.MILLISECONDS);
|
||||
|
||||
headers.forEach(header -> {
|
||||
String[] keyValuePair = header.split("=", 2);
|
||||
if (keyValuePair.length == 2) {
|
||||
request.header(keyValuePair[0].trim(), keyValuePair[1].trim());
|
||||
} else {
|
||||
logger.warn("Splitting header '{}' failed. No '=' was found. Ignoring", header);
|
||||
}
|
||||
});
|
||||
|
||||
CompletableFuture<@Nullable Content> response = new CompletableFuture<>();
|
||||
response.exceptionally(e -> {
|
||||
if (e instanceof HttpAuthException) {
|
||||
if (isRetry) {
|
||||
logger.warn("Retry after authentication failure failed again for '{}', failing here",
|
||||
finalUrl);
|
||||
} else {
|
||||
AuthenticationStore authStore = httpClient.getAuthenticationStore();
|
||||
Authentication.Result authResult = authStore.findAuthenticationResult(finalUrl);
|
||||
if (authResult != null) {
|
||||
authStore.removeAuthenticationResult(authResult);
|
||||
logger.debug("Cleared authentication result for '{}', retrying immediately", finalUrl);
|
||||
refresh(true);
|
||||
} else {
|
||||
logger.warn("Could not find authentication result for '{}', failing here", finalUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}).thenAccept(this::processResult);
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Sending to '{}': {}", finalUrl, Util.requestToLogString(request));
|
||||
}
|
||||
|
||||
request.send(new HttpResponseListener(response, fallbackEncoding));
|
||||
} catch (IllegalArgumentException | URISyntaxException e) {
|
||||
logger.warn("Creating request for '{}' failed: {}", url, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
// clearing all listeners to prevent further updates
|
||||
consumers.clear();
|
||||
future.cancel(false);
|
||||
logger.trace("Stopped refresh task for URL '{}'", url);
|
||||
}
|
||||
|
||||
public void addConsumer(Consumer<Content> consumer) {
|
||||
consumers.add(consumer);
|
||||
}
|
||||
|
||||
public Optional<Content> get() {
|
||||
final Content content = lastContent;
|
||||
if (content == null) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return Optional.of(content);
|
||||
}
|
||||
}
|
||||
|
||||
private void processResult(@Nullable Content content) {
|
||||
if (content != null) {
|
||||
for (Consumer<Content> consumer : consumers) {
|
||||
try {
|
||||
consumer.accept(content);
|
||||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
logger.warn("Failed processing result for URL {}: {}", url, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
lastContent = content;
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 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.http.internal.transform;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.transform.TransformationService;
|
||||
|
||||
/**
|
||||
* The {@link CascadedValueTransformationImpl} implements {@link ValueTransformation for a cascaded set of
|
||||
* transformations}
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CascadedValueTransformationImpl implements ValueTransformation {
|
||||
private final List<ValueTransformation> transformations;
|
||||
|
||||
public CascadedValueTransformationImpl(String transformationString,
|
||||
Function<String, @Nullable TransformationService> transformationServiceSupplier) {
|
||||
transformations = Arrays.stream(transformationString.split("∩")).filter(s -> !s.isEmpty())
|
||||
.map(transformation -> new SingleValueTransformation(transformation, transformationServiceSupplier))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> apply(String value) {
|
||||
Optional<String> valueOptional = Optional.of(value);
|
||||
|
||||
// process all transformations
|
||||
for (ValueTransformation transformation : transformations) {
|
||||
valueOptional = valueOptional.flatMap(transformation::apply);
|
||||
}
|
||||
|
||||
return valueOptional;
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.transform;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link NoOpValueTransformation} implements a no-op (identity) transformation
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NoOpValueTransformation implements ValueTransformation {
|
||||
private static final NoOpValueTransformation NO_OP_VALUE_TRANSFORMATION = new NoOpValueTransformation();
|
||||
|
||||
@Override
|
||||
public Optional<String> apply(String value) {
|
||||
return Optional.of(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the static value transformation for identity
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static ValueTransformation getInstance() {
|
||||
return NO_OP_VALUE_TRANSFORMATION;
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 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.http.internal.transform;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.transform.TransformationException;
|
||||
import org.openhab.core.transform.TransformationService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A transformation for a value used in {@HttpChannel}.
|
||||
*
|
||||
* @author David Graeff - Initial contribution
|
||||
* @author Jan N. Klug - adapted from MQTT binding to HTTP binding
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SingleValueTransformation implements ValueTransformation {
|
||||
private final Logger logger = LoggerFactory.getLogger(SingleValueTransformation.class);
|
||||
private final Function<String, @Nullable TransformationService> transformationServiceSupplier;
|
||||
private WeakReference<@Nullable TransformationService> transformationService = new WeakReference<>(null);
|
||||
private final String pattern;
|
||||
private final String serviceName;
|
||||
|
||||
/**
|
||||
* Creates a new channel state transformer.
|
||||
*
|
||||
* @param pattern A transformation pattern, starting with the transformation service
|
||||
* name, followed by a colon and the transformation itself.
|
||||
* @param transformationServiceSupplier
|
||||
*/
|
||||
public SingleValueTransformation(String pattern,
|
||||
Function<String, @Nullable TransformationService> transformationServiceSupplier) {
|
||||
this.transformationServiceSupplier = transformationServiceSupplier;
|
||||
int index = pattern.indexOf(':');
|
||||
if (index == -1) {
|
||||
throw new IllegalArgumentException(
|
||||
"The transformation pattern must consist of the type and the pattern separated by a colon");
|
||||
}
|
||||
this.serviceName = pattern.substring(0, index).toUpperCase();
|
||||
this.pattern = pattern.substring(index + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> apply(String value) {
|
||||
TransformationService transformationService = this.transformationService.get();
|
||||
if (transformationService == null) {
|
||||
transformationService = transformationServiceSupplier.apply(serviceName);
|
||||
if (transformationService == null) {
|
||||
logger.warn("Transformation service {} for pattern {} not found!", serviceName, pattern);
|
||||
return Optional.empty();
|
||||
}
|
||||
this.transformationService = new WeakReference<>(transformationService);
|
||||
}
|
||||
|
||||
try {
|
||||
String result = transformationService.transform(pattern, value);
|
||||
if (result == null) {
|
||||
logger.debug("Transformation {} returned empty result when applied to {}.", this, value);
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(result);
|
||||
} catch (TransformationException e) {
|
||||
logger.warn("Executing transformation {} failed: {}", this, e.getMessage());
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChannelStateTransformation{pattern='" + pattern + "', serviceName='" + serviceName + "'}";
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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.http.internal.transform;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link ValueTransformation} applies a set of transformations to a value
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ValueTransformation {
|
||||
|
||||
/**
|
||||
* applies the value transformation to a value
|
||||
*
|
||||
* @param value The value
|
||||
* @return Optional of string representing the transformed value (empty if transformation not present or failed)
|
||||
*/
|
||||
Optional<String> apply(String value);
|
||||
}
|
@ -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.http.internal.transform;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link ValueTransformationProvider} allows to retrieve a transformation service by name
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ValueTransformationProvider {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param pattern A transformation pattern, starting with the transformation service
|
||||
* * name, followed by a colon and the transformation itself.
|
||||
* @return
|
||||
*/
|
||||
ValueTransformation getValueTransformation(@Nullable String pattern);
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="http" 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>HTTP Binding</name>
|
||||
<description>This is the binding for retrieving and processing HTTP resources.</description>
|
||||
<author>Jan N. Klug</author>
|
||||
|
||||
</binding:binding>
|
@ -0,0 +1,350 @@
|
||||
<?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="channel-type:http:channel-config">
|
||||
<parameter name="stateExtension" type="text">
|
||||
<label>State URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="commandExtension" type="text">
|
||||
<label>Command URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for sending values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="stateTransformation" type="text">
|
||||
<label>State Transformation</label>
|
||||
<description>Transformation pattern used when receiving values.</description>
|
||||
</parameter>
|
||||
<parameter name="commandTransformation" type="text">
|
||||
<label>Command Transformation</label>
|
||||
<description>Transformation pattern used when sending values.</description>
|
||||
</parameter>
|
||||
<parameter name="mode" type="text">
|
||||
<label>Read/Write Mode</label>
|
||||
<options>
|
||||
<option value="READWRITE">Read/Write</option>
|
||||
<option value="READONLY">Read Only</option>
|
||||
<option value="WRITEONLY">Write Only</option>
|
||||
</options>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<advanced>true</advanced>
|
||||
<default>READWRITE</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="channel-type:http:channel-config-color">
|
||||
<parameter name="stateExtension" type="text">
|
||||
<label>State URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="commandExtension" type="text">
|
||||
<label>Command URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for sending values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="stateTransformation" type="text">
|
||||
<label>State Transformation</label>
|
||||
<description>Transformation pattern used when receiving values.</description>
|
||||
</parameter>
|
||||
<parameter name="commandTransformation" type="text">
|
||||
<label>Command Transformation</label>
|
||||
<description>Transformation pattern used when sending values.</description>
|
||||
</parameter>
|
||||
<parameter name="onValue" type="text">
|
||||
<label>On Value</label>
|
||||
<description>The value that represents ON</description>
|
||||
</parameter>
|
||||
<parameter name="offValue" type="text">
|
||||
<label>Off Value</label>
|
||||
<description>The value that represents OFF</description>
|
||||
</parameter>
|
||||
<parameter name="increaseValue" type="text">
|
||||
<label>Increase Value</label>
|
||||
<description>The value that represents INCREASE</description>
|
||||
</parameter>
|
||||
<parameter name="decreaseValue" type="text">
|
||||
<label>Decrease Value</label>
|
||||
<description>The value that represents DECREASE</description>
|
||||
</parameter>
|
||||
<parameter name="step" type="text">
|
||||
<label>Increase/Decrease Step</label>
|
||||
<description>The value by which the current brightness is increased/decreased if the corresponding command is
|
||||
received</description>
|
||||
<default>1</default>
|
||||
</parameter>
|
||||
<parameter name="colorMode" type="text">
|
||||
<label>Color Mode</label>
|
||||
<description>Color mode for parsing incoming and sending outgoing values</description>
|
||||
<options>
|
||||
<option value="HSB">HSB</option>
|
||||
<option value="RGB">RGB</option>
|
||||
</options>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<default>RGB</default>
|
||||
</parameter>
|
||||
<parameter name="mode" type="text">
|
||||
<label>Read/Write Mode</label>
|
||||
<options>
|
||||
<option value="READWRITE">Read/Write</option>
|
||||
<option value="READONLY">Read Only</option>
|
||||
<option value="WRITEONLY">Write Only</option>
|
||||
</options>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<advanced>true</advanced>
|
||||
<default>READWRITE</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="channel-type:http:channel-config-contact">
|
||||
<parameter name="stateExtension" type="text">
|
||||
<label>State URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="commandExtension" type="text">
|
||||
<label>Command URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for sending values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="stateTransformation" type="text">
|
||||
<label>State Transformation</label>
|
||||
<description>Transformation pattern used when receiving values.</description>
|
||||
</parameter>
|
||||
<parameter name="commandTransformation" type="text">
|
||||
<label>Command Transformation</label>
|
||||
<description>Transformation pattern used when sending values.</description>
|
||||
</parameter>
|
||||
<parameter name="openValue" type="text" required="true">
|
||||
<label>Open Value</label>
|
||||
<description>The value that represents OPEN</description>
|
||||
</parameter>
|
||||
<parameter name="closedValue" type="text" required="true">
|
||||
<label>Closed Value</label>
|
||||
<description>The value that represents CLOSED</description>
|
||||
</parameter>
|
||||
<parameter name="mode" type="text">
|
||||
<label>Read/Write Mode</label>
|
||||
<options>
|
||||
<option value="READWRITE">Read/Write</option>
|
||||
<option value="READONLY">Read Only</option>
|
||||
<option value="WRITEONLY">Write Only</option>
|
||||
</options>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<advanced>true</advanced>
|
||||
<default>READWRITE</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="channel-type:http:channel-config-dimmer">
|
||||
<parameter name="stateExtension" type="text">
|
||||
<label>State URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="commandExtension" type="text">
|
||||
<label>Command URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for sending values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="stateTransformation" type="text">
|
||||
<label>State Transformation</label>
|
||||
<description>Transformation pattern used when receiving values.</description>
|
||||
</parameter>
|
||||
<parameter name="commandTransformation" type="text">
|
||||
<label>Command Transformation</label>
|
||||
<description>Transformation pattern used when sending values.</description>
|
||||
</parameter>
|
||||
<parameter name="onValue" type="text">
|
||||
<label>On Value</label>
|
||||
<description>The value that represents ON</description>
|
||||
</parameter>
|
||||
<parameter name="offValue" type="text">
|
||||
<label>Off Value</label>
|
||||
<description>The value that represents OFF</description>
|
||||
</parameter>
|
||||
<parameter name="increaseValue" type="text">
|
||||
<label>Increase Value</label>
|
||||
<description>The value that represents INCREASE</description>
|
||||
</parameter>
|
||||
<parameter name="decreaseValue" type="text">
|
||||
<label>Decrease Value</label>
|
||||
<description>The value that represents DECREASE</description>
|
||||
</parameter>
|
||||
<parameter name="step" type="text">
|
||||
<label>Increase/Decrease Step</label>
|
||||
<description>The value by which the current brightness is increased/decreased if the corresponding command is
|
||||
received</description>
|
||||
<default>1</default>
|
||||
</parameter>
|
||||
<parameter name="mode" type="text">
|
||||
<label>Read/Write Mode</label>
|
||||
<options>
|
||||
<option value="READWRITE">Read/Write</option>
|
||||
<option value="READONLY">Read Only</option>
|
||||
<option value="WRITEONLY">Write Only</option>
|
||||
</options>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<advanced>true</advanced>
|
||||
<default>READWRITE</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="channel-type:http:channel-config-image">
|
||||
<parameter name="stateExtension" type="text">
|
||||
<label>State URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="channel-type:http:channel-config-player">
|
||||
<parameter name="stateExtension" type="text">
|
||||
<label>State URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="commandExtension" type="text">
|
||||
<label>Command URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for sending values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="stateTransformation" type="text">
|
||||
<label>State Transformation</label>
|
||||
<description>Transformation pattern used when receiving values.</description>
|
||||
</parameter>
|
||||
<parameter name="commandTransformation" type="text">
|
||||
<label>Command Transformation</label>
|
||||
<description>Transformation pattern used when sending values.</description>
|
||||
</parameter>
|
||||
|
||||
<parameter name="playValue" type="text">
|
||||
<label>Play Value</label>
|
||||
<description>The value that represents PLAY</description>
|
||||
</parameter>
|
||||
<parameter name="pauseValue" type="text">
|
||||
<label>Pause Value</label>
|
||||
<description>The value that represents PAUSE</description>
|
||||
</parameter>
|
||||
<parameter name="nextValue" type="text">
|
||||
<label>Next Value</label>
|
||||
<description>The value that represents NEXT</description>
|
||||
</parameter>
|
||||
<parameter name="previousValue" type="text">
|
||||
<label>Previous Value</label>
|
||||
<description>The value that represents PREVIOUS</description>
|
||||
</parameter>
|
||||
<parameter name="rewindValue" type="text">
|
||||
<label>Rewind Value</label>
|
||||
<description>The value that represents REWIND</description>
|
||||
</parameter>
|
||||
<parameter name="fastforwardValue" type="text">
|
||||
<label>Fast Forward Value</label>
|
||||
<description>The value that represents FASTFORWARD</description>
|
||||
</parameter>
|
||||
<parameter name="mode" type="text">
|
||||
<label>Read/Write Mode</label>
|
||||
<options>
|
||||
<option value="READWRITE">Read/Write</option>
|
||||
<option value="READONLY">Read Only</option>
|
||||
<option value="WRITEONLY">Write Only</option>
|
||||
</options>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<advanced>true</advanced>
|
||||
<default>READWRITE</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="channel-type:http:channel-config-rollershutter">
|
||||
<parameter name="stateExtension" type="text">
|
||||
<label>State URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="commandExtension" type="text">
|
||||
<label>Command URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for sending values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="stateTransformation" type="text">
|
||||
<label>State Transformation</label>
|
||||
<description>Transformation pattern used when receiving values.</description>
|
||||
</parameter>
|
||||
<parameter name="commandTransformation" type="text">
|
||||
<label>Command Transformation</label>
|
||||
<description>Transformation pattern used when sending values.</description>
|
||||
</parameter>
|
||||
<parameter name="upValue" type="text">
|
||||
<label>Up Value</label>
|
||||
<description>The value that represents UP</description>
|
||||
</parameter>
|
||||
<parameter name="downValue" type="text">
|
||||
<label>Down Value</label>
|
||||
<description>The value that represents DOWN</description>
|
||||
</parameter>
|
||||
<parameter name="stopValue" type="text">
|
||||
<label>Stop Value</label>
|
||||
<description>The value that represents STOP</description>
|
||||
</parameter>
|
||||
<parameter name="moveValue" type="text">
|
||||
<label>Move Value</label>
|
||||
<description>The value that represents MOVE</description>
|
||||
</parameter>
|
||||
<parameter name="mode" type="text">
|
||||
<label>Read/Write Mode</label>
|
||||
<options>
|
||||
<option value="READWRITE">Read/Write</option>
|
||||
<option value="READONLY">Read Only</option>
|
||||
<option value="WRITEONLY">Write Only</option>
|
||||
</options>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<advanced>true</advanced>
|
||||
<default>READWRITE</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="channel-type:http:channel-config-switch">
|
||||
<parameter name="stateExtension" type="text">
|
||||
<label>State URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="commandExtension" type="text">
|
||||
<label>Command URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for sending values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="stateTransformation" type="text">
|
||||
<label>State Transformation</label>
|
||||
<description>Transformation pattern used when receiving values.</description>
|
||||
</parameter>
|
||||
<parameter name="commandTransformation" type="text">
|
||||
<label>Command Transformation</label>
|
||||
<description>Transformation pattern used when sending values.</description>
|
||||
</parameter>
|
||||
<parameter name="onValue" type="text" required="true">
|
||||
<label>On Value</label>
|
||||
<description>The value that represents ON</description>
|
||||
</parameter>
|
||||
<parameter name="offValue" type="text" required="true">
|
||||
<label>Off Value</label>
|
||||
<description>The value that represents OFF</description>
|
||||
</parameter>
|
||||
<parameter name="mode" type="text">
|
||||
<label>Read/Write Mode</label>
|
||||
<options>
|
||||
<option value="READWRITE">Read/Write</option>
|
||||
<option value="READONLY">Read Only</option>
|
||||
<option value="WRITEONLY">Write Only</option>
|
||||
</options>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<advanced>true</advanced>
|
||||
<default>READWRITE</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</config-description:config-descriptions>
|
@ -0,0 +1,158 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="http"
|
||||
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">
|
||||
|
||||
<thing-type id="url"
|
||||
extensible="color,contact,datetime,dimmer,image,location,number,rollershutter,string,switch">
|
||||
<label>HTTP URL Thing</label>
|
||||
<description>Represents a base URL and all associated requests.</description>
|
||||
|
||||
<config-description>
|
||||
<parameter name="baseURL" type="text" required="true">
|
||||
<label>Base URL</label>
|
||||
<description>The URL set here can be extended in the channel configuration.</description>
|
||||
<context>url</context>
|
||||
</parameter>
|
||||
<parameter name="refresh" type="integer" unit="s" min="1">
|
||||
<label>Refresh Time</label>
|
||||
<description>Time between two refreshes of all channels</description>
|
||||
<default>30</default>
|
||||
</parameter>
|
||||
<parameter name="timeout" type="integer" unit="ms" min="0">
|
||||
<label>Timeout</label>
|
||||
<description>The timeout in ms for each request</description>
|
||||
<default>3000</default>
|
||||
</parameter>
|
||||
<parameter name="username" type="text">
|
||||
<label>Username</label>
|
||||
<description>Basic Authentication username</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="password" type="text">
|
||||
<label>Password</label>
|
||||
<description>Basic Authentication password</description>
|
||||
<context>password</context>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="authMode" type="text">
|
||||
<label>Authentication Mode</label>
|
||||
<options>
|
||||
<option value="BASIC">Basic Authentication</option>
|
||||
<option value="DIGEST">Digest Authentication</option>
|
||||
</options>
|
||||
<default>BASIC</default>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="commandMethod" type="text">
|
||||
<label>Command Method</label>
|
||||
<description>HTTP method (GET,POST, PUT) for sending commands.</description>
|
||||
<options>
|
||||
<option value="GET">GET</option>
|
||||
<option value="POST">POST</option>
|
||||
<option value="PUT">PUT</option>
|
||||
</options>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<default>GET</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="contentType" type="text">
|
||||
<label>Content Type</label>
|
||||
<description>The MIME content type. Only used for `POST` and `PUT`.</description>
|
||||
<options>
|
||||
<option value="application/json">application/json</option>
|
||||
<option value="application/xml">application/xml</option>
|
||||
<option value="text/html">text/html</option>
|
||||
<option value="text/plain">text/plain</option>
|
||||
<option value="text/xml">text/xml</option>
|
||||
</options>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="encoding" type="text">
|
||||
<label>Fallback Encoding</label>
|
||||
<description>Fallback Encoding text received by this thing's channels.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="headers" type="text" multiple="true">
|
||||
<label>Headers</label>
|
||||
<description>Additional headers send along with the request</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="ignoreSSLErrors" type="boolean">
|
||||
<label>Ignore SSL Errors</label>
|
||||
<description>If set to true ignores invalid SSL certificate errors. This is potentially dangerous.</description>
|
||||
<default>false</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="color">
|
||||
<item-type>Color</item-type>
|
||||
<label>Color Channel</label>
|
||||
<config-description-ref uri="channel-type:http:channel-config-color"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="contact">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Contact Channel</label>
|
||||
<config-description-ref uri="channel-type:http:channel-config-contact"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="datetime">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>DateTime Channel</label>
|
||||
<config-description-ref uri="channel-type:http:channel-config"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dimmer">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Dimmer Channel</label>
|
||||
<config-description-ref uri="channel-type:http:channel-config-dimmer"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="image">
|
||||
<item-type>Image</item-type>
|
||||
<label>Image Channel</label>
|
||||
<config-description-ref uri="channel-type:http:channel-config-image"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="location">
|
||||
<item-type>Location</item-type>
|
||||
<label>Location Channel</label>
|
||||
<config-description-ref uri="channel-type:http:channel-config"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="number">
|
||||
<item-type>Number</item-type>
|
||||
<label>Number Channel</label>
|
||||
<config-description-ref uri="channel-type:http:channel-config"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="player">
|
||||
<item-type>Player</item-type>
|
||||
<label>Player Channel</label>
|
||||
<config-description-ref uri="channel-type:http:channel-config-player"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rollershutter">
|
||||
<item-type>Rollershutter</item-type>
|
||||
<label>Rollershutter Channel</label>
|
||||
<config-description-ref uri="channel-type:http:channel-config-rollershutter"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="string">
|
||||
<item-type>String</item-type>
|
||||
<label>String Channel</label>
|
||||
<config-description-ref uri="channel-type:http:channel-config"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="switch">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Switch Channel</label>
|
||||
<config-description-ref uri="channel-type:http:channel-config-switch"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.converter;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.http.internal.config.HttpChannelConfig;
|
||||
import org.openhab.binding.http.internal.transform.NoOpValueTransformation;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link ConverterTest} is a test class for state converters
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ConverterTest {
|
||||
|
||||
@Test
|
||||
public void stringTypeConverter() {
|
||||
GenericItemConverter converter = createConverter(StringType::new);
|
||||
Assertions.assertEquals(new StringType("Test"), converter.toState("Test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decimalTypeConverter() {
|
||||
GenericItemConverter converter = createConverter(DecimalType::new);
|
||||
Assertions.assertEquals(new DecimalType(15.6), converter.toState("15.6"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pointTypeConverter() {
|
||||
GenericItemConverter converter = createConverter(PointType::new);
|
||||
Assertions.assertEquals(new PointType(new DecimalType(51.1), new DecimalType(7.2), new DecimalType(100)),
|
||||
converter.toState("51.1, 7.2, 100"));
|
||||
}
|
||||
|
||||
private void sendHttpValue(String value) {
|
||||
}
|
||||
|
||||
private void updateState(State state) {
|
||||
}
|
||||
|
||||
public void postCommand(Command command) {
|
||||
}
|
||||
|
||||
public GenericItemConverter createConverter(Function<String, State> fcn) {
|
||||
return new GenericItemConverter(fcn, this::updateState, this::postCommand, this::sendHttpValue,
|
||||
NoOpValueTransformation.getInstance(), NoOpValueTransformation.getInstance(), new HttpChannelConfig());
|
||||
}
|
||||
}
|
@ -121,6 +121,7 @@
|
||||
<module>org.openhab.binding.heos</module>
|
||||
<module>org.openhab.binding.homematic</module>
|
||||
<module>org.openhab.binding.hpprinter</module>
|
||||
<module>org.openhab.binding.http</module>
|
||||
<module>org.openhab.binding.hue</module>
|
||||
<module>org.openhab.binding.hydrawise</module>
|
||||
<module>org.openhab.binding.hyperion</module>
|
||||
|
Loading…
Reference in New Issue
Block a user