[http] Initial contribution (#8521)

Signed-off-by: Jan N. Klug <jan.n.klug@rub.de>
This commit is contained in:
J-N-K
2020-11-09 17:53:44 +01:00
committed by GitHub
parent 3a4eaae1e9
commit 8de5652ed1
39 changed files with 3026 additions and 0 deletions

View File

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

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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");
}

View File

@@ -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();
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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;
}
}

View File

@@ -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
}

View File

@@ -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);
}
}
}

View File

@@ -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
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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();
}
}

View File

@@ -0,0 +1,48 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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");
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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;
}
}

View File

@@ -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 + "'}";
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

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

View File

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

View File

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