[http] Initial contribution (#8521)
Signed-off-by: Jan N. Klug <jan.n.klug@rub.de>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.http-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-http" description="HTTP Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.http/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link HttpBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HttpBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "http";
|
||||
|
||||
public static final ThingTypeUID THING_TYPE_URL = new ThingTypeUID(BINDING_ID, "url");
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
|
||||
/**
|
||||
* The {@link HttpClientProvider} defines the interface for providing {@link HttpClient} instances to thing handlers
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface HttpClientProvider {
|
||||
|
||||
/**
|
||||
* get the secure http client
|
||||
*
|
||||
* @return a HttpClient
|
||||
*/
|
||||
HttpClient getSecureClient();
|
||||
|
||||
/**
|
||||
* get the insecure http client (ignores SSL errors)
|
||||
*
|
||||
* @return q HttpClient
|
||||
*/
|
||||
HttpClient getInsecureClient();
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
|
||||
import org.openhab.core.types.StateDescription;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Dynamic channel state description provider.
|
||||
* Overrides the state description for the controls, which receive its configuration in the runtime.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { DynamicStateDescriptionProvider.class,
|
||||
HttpDynamicStateDescriptionProvider.class }, immediate = true)
|
||||
public class HttpDynamicStateDescriptionProvider implements DynamicStateDescriptionProvider {
|
||||
|
||||
private final Map<ChannelUID, StateDescription> descriptions = new ConcurrentHashMap<>();
|
||||
private final Logger logger = LoggerFactory.getLogger(HttpDynamicStateDescriptionProvider.class);
|
||||
|
||||
/**
|
||||
* Set a state description for a channel. This description will be used when preparing the channel state by
|
||||
* the framework for presentation. A previous description, if existed, will be replaced.
|
||||
*
|
||||
* @param channelUID
|
||||
* channel UID
|
||||
* @param description
|
||||
* state description for the channel
|
||||
*/
|
||||
public void setDescription(ChannelUID channelUID, StateDescription description) {
|
||||
logger.trace("adding state description for channel {}", channelUID);
|
||||
descriptions.put(channelUID, description);
|
||||
}
|
||||
|
||||
/**
|
||||
* remove all descriptions for a given thing
|
||||
*
|
||||
* @param thingUID the thing's UID
|
||||
*/
|
||||
public void removeDescriptionsForThing(ThingUID thingUID) {
|
||||
logger.trace("removing state description for thing {}", thingUID);
|
||||
descriptions.entrySet().removeIf(entry -> entry.getKey().getThingUID().equals(thingUID));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable StateDescription getStateDescription(Channel channel,
|
||||
@Nullable StateDescription originalStateDescription, @Nullable Locale locale) {
|
||||
if (descriptions.containsKey(channel.getUID())) {
|
||||
logger.trace("returning new stateDescription for {}", channel.getUID());
|
||||
return descriptions.get(channel.getUID());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal;
|
||||
|
||||
import static org.openhab.binding.http.internal.HttpBindingConstants.*;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.openhab.binding.http.internal.transform.CascadedValueTransformationImpl;
|
||||
import org.openhab.binding.http.internal.transform.NoOpValueTransformation;
|
||||
import org.openhab.binding.http.internal.transform.ValueTransformation;
|
||||
import org.openhab.binding.http.internal.transform.ValueTransformationProvider;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.openhab.core.transform.TransformationHelper;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link HttpHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.http", service = ThingHandlerFactory.class)
|
||||
public class HttpHandlerFactory extends BaseThingHandlerFactory
|
||||
implements ValueTransformationProvider, HttpClientProvider {
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_URL);
|
||||
private final Logger logger = LoggerFactory.getLogger(HttpHandlerFactory.class);
|
||||
|
||||
private final HttpClient secureClient;
|
||||
private final HttpClient insecureClient;
|
||||
|
||||
private final HttpDynamicStateDescriptionProvider httpDynamicStateDescriptionProvider;
|
||||
|
||||
@Activate
|
||||
public HttpHandlerFactory(@Reference HttpClientFactory httpClientFactory,
|
||||
@Reference HttpDynamicStateDescriptionProvider httpDynamicStateDescriptionProvider) {
|
||||
this.secureClient = new HttpClient(new SslContextFactory());
|
||||
this.insecureClient = new HttpClient(new SslContextFactory(true));
|
||||
try {
|
||||
this.secureClient.start();
|
||||
this.insecureClient.start();
|
||||
} catch (Exception e) {
|
||||
// catching exception is necessary due to the signature of HttpClient.start()
|
||||
logger.warn("Failed to start insecure http client: {}", e.getMessage());
|
||||
throw new IllegalStateException("Could not create insecure HttpClient");
|
||||
}
|
||||
this.httpDynamicStateDescriptionProvider = httpDynamicStateDescriptionProvider;
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
public void deactivate() {
|
||||
try {
|
||||
secureClient.stop();
|
||||
insecureClient.stop();
|
||||
} catch (Exception e) {
|
||||
// catching exception is necessary due to the signature of HttpClient.stop()
|
||||
logger.warn("Failed to stop insecure http client: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (THING_TYPE_URL.equals(thingTypeUID)) {
|
||||
return new HttpThingHandler(thing, this, this, httpDynamicStateDescriptionProvider);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueTransformation getValueTransformation(@Nullable String pattern) {
|
||||
if (pattern == null) {
|
||||
return NoOpValueTransformation.getInstance();
|
||||
}
|
||||
return new CascadedValueTransformationImpl(pattern,
|
||||
name -> TransformationHelper.getTransformationService(bundleContext, name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient getSecureClient() {
|
||||
return secureClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient getInsecureClient() {
|
||||
return insecureClient;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.Authentication;
|
||||
import org.eclipse.jetty.client.api.AuthenticationStore;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.BasicAuthentication;
|
||||
import org.eclipse.jetty.client.util.DigestAuthentication;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.http.internal.config.HttpChannelConfig;
|
||||
import org.openhab.binding.http.internal.config.HttpChannelMode;
|
||||
import org.openhab.binding.http.internal.config.HttpThingConfig;
|
||||
import org.openhab.binding.http.internal.converter.AbstractTransformingItemConverter;
|
||||
import org.openhab.binding.http.internal.converter.ColorItemConverter;
|
||||
import org.openhab.binding.http.internal.converter.DimmerItemConverter;
|
||||
import org.openhab.binding.http.internal.converter.FixedValueMappingItemConverter;
|
||||
import org.openhab.binding.http.internal.converter.GenericItemConverter;
|
||||
import org.openhab.binding.http.internal.converter.ImageItemConverter;
|
||||
import org.openhab.binding.http.internal.converter.ItemValueConverter;
|
||||
import org.openhab.binding.http.internal.converter.PlayerItemConverter;
|
||||
import org.openhab.binding.http.internal.converter.RollershutterItemConverter;
|
||||
import org.openhab.binding.http.internal.http.Content;
|
||||
import org.openhab.binding.http.internal.http.HttpAuthException;
|
||||
import org.openhab.binding.http.internal.http.HttpResponseListener;
|
||||
import org.openhab.binding.http.internal.http.RefreshingUrlCache;
|
||||
import org.openhab.binding.http.internal.transform.ValueTransformationProvider;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.StateDescription;
|
||||
import org.openhab.core.types.StateDescriptionFragmentBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link HttpThingHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HttpThingHandler extends BaseThingHandler {
|
||||
private static final Set<Character> URL_PART_DELIMITER = Set.of('/', '?', '&');
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(HttpThingHandler.class);
|
||||
private final ValueTransformationProvider valueTransformationProvider;
|
||||
private final HttpClientProvider httpClientProvider;
|
||||
private HttpClient httpClient;
|
||||
private final HttpDynamicStateDescriptionProvider httpDynamicStateDescriptionProvider;
|
||||
|
||||
private HttpThingConfig config = new HttpThingConfig();
|
||||
private final Map<String, RefreshingUrlCache> urlHandlers = new HashMap<>();
|
||||
private final Map<ChannelUID, ItemValueConverter> channels = new HashMap<>();
|
||||
private final Map<ChannelUID, String> channelUrls = new HashMap<>();
|
||||
private @Nullable Authentication authentication;
|
||||
|
||||
public HttpThingHandler(Thing thing, HttpClientProvider httpClientProvider,
|
||||
ValueTransformationProvider valueTransformationProvider,
|
||||
HttpDynamicStateDescriptionProvider httpDynamicStateDescriptionProvider) {
|
||||
super(thing);
|
||||
this.httpClientProvider = httpClientProvider;
|
||||
this.httpClient = httpClientProvider.getSecureClient();
|
||||
this.valueTransformationProvider = valueTransformationProvider;
|
||||
this.httpDynamicStateDescriptionProvider = httpDynamicStateDescriptionProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
ItemValueConverter itemValueConverter = channels.get(channelUID);
|
||||
if (itemValueConverter == null) {
|
||||
logger.warn("Cannot find channel implementation for channel {}.", channelUID);
|
||||
return;
|
||||
}
|
||||
|
||||
if (command instanceof RefreshType) {
|
||||
String stateUrl = channelUrls.get(channelUID);
|
||||
if (stateUrl != null) {
|
||||
RefreshingUrlCache refreshingUrlCache = urlHandlers.get(stateUrl);
|
||||
if (refreshingUrlCache != null) {
|
||||
try {
|
||||
refreshingUrlCache.get().ifPresent(itemValueConverter::process);
|
||||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
logger.warn("Failed processing REFRESH command for channel {}: {}", channelUID, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
itemValueConverter.send(command);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.warn("Failed to convert command '{}' to channel '{}' for sending", command, channelUID);
|
||||
} catch (IllegalStateException e) {
|
||||
logger.debug("Writing to read-only channel {} not permitted", channelUID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(HttpThingConfig.class);
|
||||
|
||||
if (config.baseURL.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Parameter baseURL must not be empty!");
|
||||
return;
|
||||
}
|
||||
authentication = null;
|
||||
if (!config.username.isEmpty()) {
|
||||
try {
|
||||
URI uri = new URI(config.baseURL);
|
||||
switch (config.authMode) {
|
||||
case BASIC:
|
||||
authentication = new BasicAuthentication(uri, Authentication.ANY_REALM, config.username,
|
||||
config.password);
|
||||
logger.debug("Basic Authentication configured for thing '{}'", thing.getUID());
|
||||
break;
|
||||
case DIGEST:
|
||||
authentication = new DigestAuthentication(uri, Authentication.ANY_REALM, config.username,
|
||||
config.password);
|
||||
logger.debug("Digest Authentication configured for thing '{}'", thing.getUID());
|
||||
break;
|
||||
default:
|
||||
logger.warn("Unknown authentication method '{}' for thing '{}'", config.authMode,
|
||||
thing.getUID());
|
||||
}
|
||||
if (authentication != null) {
|
||||
AuthenticationStore authStore = httpClient.getAuthenticationStore();
|
||||
authStore.addAuthentication(authentication);
|
||||
}
|
||||
} catch (URISyntaxException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"failed to create authentication: baseUrl is invalid");
|
||||
}
|
||||
} else {
|
||||
logger.debug("No authentication configured for thing '{}'", thing.getUID());
|
||||
}
|
||||
|
||||
if (config.ignoreSSLErrors) {
|
||||
logger.info("Using the insecure client for thing '{}'.", thing.getUID());
|
||||
httpClient = httpClientProvider.getInsecureClient();
|
||||
} else {
|
||||
logger.info("Using the secure client for thing '{}'.", thing.getUID());
|
||||
httpClient = httpClientProvider.getSecureClient();
|
||||
}
|
||||
|
||||
thing.getChannels().forEach(this::createChannel);
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
// stop update tasks
|
||||
urlHandlers.values().forEach(RefreshingUrlCache::stop);
|
||||
|
||||
// clear lists
|
||||
urlHandlers.clear();
|
||||
channels.clear();
|
||||
channelUrls.clear();
|
||||
|
||||
// remove state descriptions
|
||||
httpDynamicStateDescriptionProvider.removeDescriptionsForThing(thing.getUID());
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* create all necessary information to handle every channel
|
||||
*
|
||||
* @param channel a thing channel
|
||||
*/
|
||||
private void createChannel(Channel channel) {
|
||||
ChannelUID channelUID = channel.getUID();
|
||||
HttpChannelConfig channelConfig = channel.getConfiguration().as(HttpChannelConfig.class);
|
||||
|
||||
String stateUrl = concatenateUrlParts(config.baseURL, channelConfig.stateExtension);
|
||||
String commandUrl = channelConfig.commandExtension == null ? stateUrl
|
||||
: concatenateUrlParts(config.baseURL, channelConfig.commandExtension);
|
||||
|
||||
String acceptedItemType = channel.getAcceptedItemType();
|
||||
if (acceptedItemType == null) {
|
||||
logger.warn("Cannot determine item-type for channel '{}'", channelUID);
|
||||
return;
|
||||
}
|
||||
|
||||
ItemValueConverter itemValueConverter;
|
||||
switch (acceptedItemType) {
|
||||
case "Color":
|
||||
itemValueConverter = createItemConverter(ColorItemConverter::new, commandUrl, channelUID,
|
||||
channelConfig);
|
||||
break;
|
||||
case "DateTime":
|
||||
itemValueConverter = createGenericItemConverter(commandUrl, channelUID, channelConfig,
|
||||
DateTimeType::new);
|
||||
break;
|
||||
case "Dimmer":
|
||||
itemValueConverter = createItemConverter(DimmerItemConverter::new, commandUrl, channelUID,
|
||||
channelConfig);
|
||||
break;
|
||||
case "Contact":
|
||||
case "Switch":
|
||||
itemValueConverter = createItemConverter(FixedValueMappingItemConverter::new, commandUrl, channelUID,
|
||||
channelConfig);
|
||||
break;
|
||||
case "Image":
|
||||
itemValueConverter = new ImageItemConverter(state -> updateState(channelUID, state));
|
||||
break;
|
||||
case "Location":
|
||||
itemValueConverter = createGenericItemConverter(commandUrl, channelUID, channelConfig, PointType::new);
|
||||
break;
|
||||
case "Number":
|
||||
itemValueConverter = createGenericItemConverter(commandUrl, channelUID, channelConfig,
|
||||
DecimalType::new);
|
||||
break;
|
||||
case "Player":
|
||||
itemValueConverter = createItemConverter(PlayerItemConverter::new, commandUrl, channelUID,
|
||||
channelConfig);
|
||||
break;
|
||||
case "Rollershutter":
|
||||
itemValueConverter = createItemConverter(RollershutterItemConverter::new, commandUrl, channelUID,
|
||||
channelConfig);
|
||||
break;
|
||||
case "String":
|
||||
itemValueConverter = createGenericItemConverter(commandUrl, channelUID, channelConfig, StringType::new);
|
||||
break;
|
||||
default:
|
||||
logger.warn("Unsupported item-type '{}'", channel.getAcceptedItemType());
|
||||
return;
|
||||
}
|
||||
|
||||
channels.put(channelUID, itemValueConverter);
|
||||
if (channelConfig.mode != HttpChannelMode.WRITEONLY) {
|
||||
channelUrls.put(channelUID, stateUrl);
|
||||
urlHandlers.computeIfAbsent(stateUrl, url -> new RefreshingUrlCache(scheduler, httpClient, url, config))
|
||||
.addConsumer(itemValueConverter::process);
|
||||
}
|
||||
|
||||
StateDescription stateDescription = StateDescriptionFragmentBuilder.create()
|
||||
.withReadOnly(channelConfig.mode == HttpChannelMode.READONLY).build().toStateDescription();
|
||||
if (stateDescription != null) {
|
||||
// if the state description is not available, we don'tneed to add it
|
||||
httpDynamicStateDescriptionProvider.setDescription(channelUID, stateDescription);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendHttpValue(String commandUrl, String command) {
|
||||
sendHttpValue(commandUrl, command, false);
|
||||
}
|
||||
|
||||
private void sendHttpValue(String commandUrl, String command, boolean isRetry) {
|
||||
try {
|
||||
// format URL
|
||||
URI finalUrl = new URI(String.format(commandUrl, new Date(), command));
|
||||
|
||||
// build request
|
||||
Request request = httpClient.newRequest(finalUrl).timeout(config.timeout, TimeUnit.MILLISECONDS)
|
||||
.method(config.commandMethod);
|
||||
if (config.commandMethod != HttpMethod.GET) {
|
||||
final String contentType = config.contentType;
|
||||
if (contentType != null) {
|
||||
request.content(new StringContentProvider(command), contentType);
|
||||
} else {
|
||||
request.content(new StringContentProvider(command));
|
||||
}
|
||||
}
|
||||
|
||||
config.headers.forEach(header -> {
|
||||
String[] keyValuePair = header.split("=", 2);
|
||||
if (keyValuePair.length == 2) {
|
||||
request.header(keyValuePair[0], keyValuePair[1]);
|
||||
} else {
|
||||
logger.warn("Splitting header '{}' failed. No '=' was found. Ignoring", header);
|
||||
}
|
||||
});
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Sending to '{}': {}", finalUrl, Util.requestToLogString(request));
|
||||
}
|
||||
|
||||
CompletableFuture<@Nullable Content> f = new CompletableFuture<>();
|
||||
f.exceptionally(e -> {
|
||||
if (e instanceof HttpAuthException) {
|
||||
if (isRetry) {
|
||||
logger.warn("Retry after authentication failure failed again for '{}', failing here", finalUrl);
|
||||
} else {
|
||||
AuthenticationStore authStore = httpClient.getAuthenticationStore();
|
||||
Authentication.Result authResult = authStore.findAuthenticationResult(finalUrl);
|
||||
if (authResult != null) {
|
||||
authStore.removeAuthenticationResult(authResult);
|
||||
logger.debug("Cleared authentication result for '{}', retrying immediately", finalUrl);
|
||||
sendHttpValue(commandUrl, command, true);
|
||||
} else {
|
||||
logger.warn("Could not find authentication result for '{}', failing here", finalUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
request.send(new HttpResponseListener(f));
|
||||
} catch (IllegalArgumentException | URISyntaxException e) {
|
||||
logger.warn("Creating request for '{}' failed: {}", commandUrl, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String concatenateUrlParts(String baseUrl, @Nullable String extension) {
|
||||
if (extension != null && !extension.isEmpty()) {
|
||||
if (!URL_PART_DELIMITER.contains(baseUrl.charAt(baseUrl.length() - 1))
|
||||
&& !URL_PART_DELIMITER.contains(extension.charAt(0))) {
|
||||
return baseUrl + "/" + extension;
|
||||
} else {
|
||||
return baseUrl + extension;
|
||||
}
|
||||
} else {
|
||||
return baseUrl;
|
||||
}
|
||||
}
|
||||
|
||||
private ItemValueConverter createItemConverter(AbstractTransformingItemConverter.Factory factory, String commandUrl,
|
||||
ChannelUID channelUID, HttpChannelConfig channelConfig) {
|
||||
return factory.create(state -> updateState(channelUID, state), command -> postCommand(channelUID, command),
|
||||
command -> sendHttpValue(commandUrl, command),
|
||||
valueTransformationProvider.getValueTransformation(channelConfig.stateTransformation),
|
||||
valueTransformationProvider.getValueTransformation(channelConfig.commandTransformation), channelConfig);
|
||||
}
|
||||
|
||||
private ItemValueConverter createGenericItemConverter(String commandUrl, ChannelUID channelUID,
|
||||
HttpChannelConfig channelConfig, Function<String, State> toState) {
|
||||
AbstractTransformingItemConverter.Factory factory = (state, command, value, stateTrans, commandTrans,
|
||||
config) -> new GenericItemConverter(toState, state, command, value, stateTrans, commandTrans, config);
|
||||
return createItemConverter(factory, commandUrl, channelUID, channelConfig);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.api.ContentProvider;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
|
||||
/**
|
||||
* The {@link Util} is a utility class
|
||||
* channels
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Util {
|
||||
|
||||
public static String requestToLogString(Request request) {
|
||||
ContentProvider contentProvider = request.getContent();
|
||||
String contentString = contentProvider == null ? "null"
|
||||
: StreamSupport.stream(contentProvider.spliterator(), false)
|
||||
.map(b -> StandardCharsets.UTF_8.decode(b).toString()).collect(Collectors.joining(", "));
|
||||
String logString = "Method = {" + request.getMethod() + "}, Headers = {"
|
||||
+ request.getHeaders().stream().map(HttpField::toString).collect(Collectors.joining(", "))
|
||||
+ "}, Content = {" + contentString + "}";
|
||||
|
||||
return logString;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link HttpAuthMode} enum defines the method used for authentication.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum HttpAuthMode {
|
||||
BASIC,
|
||||
DIGEST
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.config;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.http.internal.converter.ColorItemConverter;
|
||||
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||
import org.openhab.core.library.types.NextPreviousType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.PlayPauseType;
|
||||
import org.openhab.core.library.types.RewindFastforwardType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link HttpChannelConfig} class contains fields mapping channel configuration parameters.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HttpChannelConfig {
|
||||
private final Map<String, State> stringStateMap = new HashMap<>();
|
||||
private final Map<Command, @Nullable String> commandStringMap = new HashMap<>();
|
||||
private boolean initialized = false;
|
||||
|
||||
public @Nullable String stateExtension;
|
||||
public @Nullable String commandExtension;
|
||||
public @Nullable String stateTransformation;
|
||||
public @Nullable String commandTransformation;
|
||||
|
||||
public HttpChannelMode mode = HttpChannelMode.READWRITE;
|
||||
|
||||
// switch, dimmer, color
|
||||
public @Nullable String onValue;
|
||||
public @Nullable String offValue;
|
||||
|
||||
// dimmer, color
|
||||
public BigDecimal step = BigDecimal.ONE;
|
||||
public @Nullable String increaseValue;
|
||||
public @Nullable String decreaseValue;
|
||||
|
||||
// color
|
||||
public ColorItemConverter.ColorMode colorMode = ColorItemConverter.ColorMode.RGB;
|
||||
|
||||
// contact
|
||||
public @Nullable String openValue;
|
||||
public @Nullable String closedValue;
|
||||
|
||||
// rollershutter
|
||||
public @Nullable String upValue;
|
||||
public @Nullable String downValue;
|
||||
public @Nullable String stopValue;
|
||||
public @Nullable String moveValue;
|
||||
|
||||
// player
|
||||
public @Nullable String playValue;
|
||||
public @Nullable String pauseValue;
|
||||
public @Nullable String nextValue;
|
||||
public @Nullable String previousValue;
|
||||
public @Nullable String rewindValue;
|
||||
public @Nullable String fastforwardValue;
|
||||
|
||||
/**
|
||||
* maps a command to a user-defined string
|
||||
*
|
||||
* @param command the command to map
|
||||
* @return a string or null if no mapping found
|
||||
*/
|
||||
public @Nullable String commandToFixedValue(Command command) {
|
||||
if (!initialized) {
|
||||
createMaps();
|
||||
}
|
||||
|
||||
return commandStringMap.get(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* maps a user-defined string to a state
|
||||
*
|
||||
* @param string the string to map
|
||||
* @return the state or null if no mapping found
|
||||
*/
|
||||
public @Nullable State fixedValueToState(String string) {
|
||||
if (!initialized) {
|
||||
createMaps();
|
||||
}
|
||||
|
||||
return stringStateMap.get(string);
|
||||
}
|
||||
|
||||
private void createMaps() {
|
||||
addToMaps(this.onValue, OnOffType.ON);
|
||||
addToMaps(this.offValue, OnOffType.OFF);
|
||||
addToMaps(this.openValue, OpenClosedType.OPEN);
|
||||
addToMaps(this.closedValue, OpenClosedType.CLOSED);
|
||||
addToMaps(this.upValue, UpDownType.UP);
|
||||
addToMaps(this.downValue, UpDownType.DOWN);
|
||||
|
||||
commandStringMap.put(IncreaseDecreaseType.INCREASE, increaseValue);
|
||||
commandStringMap.put(IncreaseDecreaseType.DECREASE, decreaseValue);
|
||||
commandStringMap.put(StopMoveType.STOP, stopValue);
|
||||
commandStringMap.put(StopMoveType.MOVE, moveValue);
|
||||
commandStringMap.put(PlayPauseType.PLAY, playValue);
|
||||
commandStringMap.put(PlayPauseType.PAUSE, pauseValue);
|
||||
commandStringMap.put(NextPreviousType.NEXT, nextValue);
|
||||
commandStringMap.put(NextPreviousType.PREVIOUS, previousValue);
|
||||
commandStringMap.put(RewindFastforwardType.REWIND, rewindValue);
|
||||
commandStringMap.put(RewindFastforwardType.FASTFORWARD, fastforwardValue);
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
private void addToMaps(@Nullable String value, State state) {
|
||||
if (value != null) {
|
||||
commandStringMap.put((Command) state, value);
|
||||
stringStateMap.put(value, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link HttpChannelMode} enum defines control modes for channels
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum HttpChannelMode {
|
||||
READONLY,
|
||||
READWRITE,
|
||||
WRITEONLY
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.config;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
|
||||
/**
|
||||
* The {@link HttpThingConfig} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HttpThingConfig {
|
||||
public String baseURL = "";
|
||||
public int refresh = 30;
|
||||
public int timeout = 3000;
|
||||
|
||||
public String username = "";
|
||||
public String password = "";
|
||||
public HttpAuthMode authMode = HttpAuthMode.BASIC;
|
||||
|
||||
public HttpMethod commandMethod = HttpMethod.GET;
|
||||
|
||||
public @Nullable String encoding = null;
|
||||
public @Nullable String contentType = null;
|
||||
|
||||
public boolean ignoreSSLErrors = false;
|
||||
|
||||
public List<String> headers = Collections.emptyList();
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.converter;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.http.internal.config.HttpChannelConfig;
|
||||
import org.openhab.binding.http.internal.config.HttpChannelMode;
|
||||
import org.openhab.binding.http.internal.http.Content;
|
||||
import org.openhab.binding.http.internal.transform.ValueTransformation;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link AbstractTransformingItemConverter} is a base class for an item converter with transformations
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class AbstractTransformingItemConverter implements ItemValueConverter {
|
||||
private final Consumer<State> updateState;
|
||||
private final Consumer<Command> postCommand;
|
||||
private final @Nullable Consumer<String> sendHttpValue;
|
||||
private final ValueTransformation stateTransformations;
|
||||
private final ValueTransformation commandTransformations;
|
||||
|
||||
protected HttpChannelConfig channelConfig;
|
||||
|
||||
public AbstractTransformingItemConverter(Consumer<State> updateState, Consumer<Command> postCommand,
|
||||
@Nullable Consumer<String> sendHttpValue, ValueTransformation stateTransformations,
|
||||
ValueTransformation commandTransformations, HttpChannelConfig channelConfig) {
|
||||
this.updateState = updateState;
|
||||
this.postCommand = postCommand;
|
||||
this.sendHttpValue = sendHttpValue;
|
||||
this.stateTransformations = stateTransformations;
|
||||
this.commandTransformations = commandTransformations;
|
||||
this.channelConfig = channelConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(Content content) {
|
||||
if (channelConfig.mode != HttpChannelMode.WRITEONLY) {
|
||||
stateTransformations.apply(content.getAsString()).ifPresent(transformedValue -> {
|
||||
Command command = toCommand(transformedValue);
|
||||
if (command != null) {
|
||||
postCommand.accept(command);
|
||||
} else {
|
||||
updateState.accept(toState(transformedValue));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw new IllegalStateException("Write-only channel");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Command command) {
|
||||
Consumer<String> sendHttpValue = this.sendHttpValue;
|
||||
if (sendHttpValue != null && channelConfig.mode != HttpChannelMode.READONLY) {
|
||||
commandTransformations.apply(toString(command)).ifPresent(sendHttpValue);
|
||||
} else {
|
||||
throw new IllegalStateException("Read-only channel");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check if this converter received a value that needs to be sent as command
|
||||
*
|
||||
* @param value the value
|
||||
* @return the command or null
|
||||
*/
|
||||
protected abstract @Nullable Command toCommand(String value);
|
||||
|
||||
/**
|
||||
* convert the received value to a state
|
||||
*
|
||||
* @param value the value
|
||||
* @return the state that represents the value of UNDEF if conversion failed
|
||||
*/
|
||||
protected abstract State toState(String value);
|
||||
|
||||
/**
|
||||
* convert a command to a string
|
||||
*
|
||||
* @param command the command
|
||||
* @return the string representation of the command
|
||||
*/
|
||||
protected abstract String toString(Command command);
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Factory {
|
||||
ItemValueConverter create(Consumer<State> updateState, Consumer<Command> postCommand,
|
||||
@Nullable Consumer<String> sendHttpValue, ValueTransformation stateTransformations,
|
||||
ValueTransformation commandTransformations, HttpChannelConfig channelConfig);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.converter;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.http.internal.config.HttpChannelConfig;
|
||||
import org.openhab.binding.http.internal.transform.ValueTransformation;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link ColorItemConverter} implements {@link org.openhab.core.library.items.ColorItem} conversions
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class ColorItemConverter extends AbstractTransformingItemConverter {
|
||||
private static final BigDecimal BYTE_FACTOR = BigDecimal.valueOf(2.55);
|
||||
private static final BigDecimal HUNDRED = BigDecimal.valueOf(100);
|
||||
private static final Pattern TRIPLE_MATCHER = Pattern.compile("(\\d+),(\\d+),(\\d+)");
|
||||
|
||||
private State state = UnDefType.UNDEF;
|
||||
|
||||
public ColorItemConverter(Consumer<State> updateState, Consumer<Command> postCommand,
|
||||
@Nullable Consumer<String> sendHttpValue, ValueTransformation stateTransformations,
|
||||
ValueTransformation commandTransformations, HttpChannelConfig channelConfig) {
|
||||
super(updateState, postCommand, sendHttpValue, stateTransformations, commandTransformations, channelConfig);
|
||||
this.channelConfig = channelConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Command toCommand(String value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Command command) {
|
||||
String string = channelConfig.commandToFixedValue(command);
|
||||
if (string != null) {
|
||||
return string;
|
||||
}
|
||||
|
||||
if (command instanceof HSBType) {
|
||||
HSBType newState = (HSBType) command;
|
||||
state = newState;
|
||||
return hsbToString(newState);
|
||||
} else if (command instanceof PercentType && state instanceof HSBType) {
|
||||
HSBType newState = new HSBType(((HSBType) state).getBrightness(), ((HSBType) state).getSaturation(),
|
||||
(PercentType) command);
|
||||
state = newState;
|
||||
return hsbToString(newState);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Command type '" + command.toString() + "' not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public State toState(String string) {
|
||||
State newState = UnDefType.UNDEF;
|
||||
if (string.equals(channelConfig.onValue)) {
|
||||
if (state instanceof HSBType) {
|
||||
newState = new HSBType(((HSBType) state).getHue(), ((HSBType) state).getSaturation(),
|
||||
PercentType.HUNDRED);
|
||||
} else {
|
||||
newState = HSBType.WHITE;
|
||||
}
|
||||
} else if (string.equals(channelConfig.offValue)) {
|
||||
if (state instanceof HSBType) {
|
||||
newState = new HSBType(((HSBType) state).getHue(), ((HSBType) state).getSaturation(), PercentType.ZERO);
|
||||
} else {
|
||||
newState = HSBType.BLACK;
|
||||
}
|
||||
} else if (string.equals(channelConfig.increaseValue) && state instanceof HSBType) {
|
||||
BigDecimal newBrightness = ((HSBType) state).getBrightness().toBigDecimal().add(channelConfig.step);
|
||||
if (HUNDRED.compareTo(newBrightness) < 0) {
|
||||
newBrightness = HUNDRED;
|
||||
}
|
||||
newState = new HSBType(((HSBType) state).getHue(), ((HSBType) state).getSaturation(),
|
||||
new PercentType(newBrightness));
|
||||
} else if (string.equals(channelConfig.decreaseValue) && state instanceof HSBType) {
|
||||
BigDecimal newBrightness = ((HSBType) state).getBrightness().toBigDecimal().subtract(channelConfig.step);
|
||||
if (BigDecimal.ZERO.compareTo(newBrightness) > 0) {
|
||||
newBrightness = BigDecimal.ZERO;
|
||||
}
|
||||
newState = new HSBType(((HSBType) state).getHue(), ((HSBType) state).getSaturation(),
|
||||
new PercentType(newBrightness));
|
||||
} else {
|
||||
Matcher matcher = TRIPLE_MATCHER.matcher(string);
|
||||
if (matcher.matches()) {
|
||||
switch (channelConfig.colorMode) {
|
||||
case RGB:
|
||||
int r = Integer.parseInt(matcher.group(0));
|
||||
int g = Integer.parseInt(matcher.group(1));
|
||||
int b = Integer.parseInt(matcher.group(2));
|
||||
newState = HSBType.fromRGB(r, g, b);
|
||||
break;
|
||||
case HSB:
|
||||
newState = new HSBType(string);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state = newState;
|
||||
return newState;
|
||||
}
|
||||
|
||||
private String hsbToString(HSBType state) {
|
||||
switch (channelConfig.colorMode) {
|
||||
case RGB:
|
||||
PercentType[] rgb = state.toRGB();
|
||||
return String.format("%1$d,%2$d,%3$d", rgb[0].toBigDecimal().multiply(BYTE_FACTOR).intValue(),
|
||||
rgb[1].toBigDecimal().multiply(BYTE_FACTOR).intValue(),
|
||||
rgb[2].toBigDecimal().multiply(BYTE_FACTOR).intValue());
|
||||
case HSB:
|
||||
return state.toString();
|
||||
}
|
||||
throw new IllegalStateException("Invalid colorMode setting");
|
||||
}
|
||||
|
||||
public enum ColorMode {
|
||||
RGB,
|
||||
HSB
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.converter;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.http.internal.config.HttpChannelConfig;
|
||||
import org.openhab.binding.http.internal.transform.ValueTransformation;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link DimmerItemConverter} implements {@link org.openhab.core.library.items.DimmerItem} conversions
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class DimmerItemConverter extends AbstractTransformingItemConverter {
|
||||
private static final BigDecimal HUNDRED = BigDecimal.valueOf(100);
|
||||
|
||||
private State state = UnDefType.UNDEF;
|
||||
|
||||
public DimmerItemConverter(Consumer<State> updateState, Consumer<Command> postCommand,
|
||||
@Nullable Consumer<String> sendHttpValue, ValueTransformation stateTransformations,
|
||||
ValueTransformation commandTransformations, HttpChannelConfig channelConfig) {
|
||||
super(updateState, postCommand, sendHttpValue, stateTransformations, commandTransformations, channelConfig);
|
||||
this.channelConfig = channelConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Command toCommand(String value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Command command) {
|
||||
String string = channelConfig.commandToFixedValue(command);
|
||||
if (string != null) {
|
||||
return string;
|
||||
}
|
||||
|
||||
if (command instanceof PercentType) {
|
||||
return ((PercentType) command).toString();
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Command type '" + command.toString() + "' not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public State toState(String string) {
|
||||
State newState = UnDefType.UNDEF;
|
||||
|
||||
if (string.equals(channelConfig.onValue)) {
|
||||
newState = PercentType.HUNDRED;
|
||||
} else if (string.equals(channelConfig.offValue)) {
|
||||
newState = PercentType.ZERO;
|
||||
} else if (string.equals(channelConfig.increaseValue) && state instanceof PercentType) {
|
||||
BigDecimal newBrightness = ((PercentType) state).toBigDecimal().add(channelConfig.step);
|
||||
if (HUNDRED.compareTo(newBrightness) < 0) {
|
||||
newBrightness = HUNDRED;
|
||||
}
|
||||
newState = new PercentType(newBrightness);
|
||||
} else if (string.equals(channelConfig.decreaseValue) && state instanceof PercentType) {
|
||||
BigDecimal newBrightness = ((PercentType) state).toBigDecimal().subtract(channelConfig.step);
|
||||
if (BigDecimal.ZERO.compareTo(newBrightness) > 0) {
|
||||
newBrightness = BigDecimal.ZERO;
|
||||
}
|
||||
newState = new PercentType(newBrightness);
|
||||
} else {
|
||||
try {
|
||||
BigDecimal value = new BigDecimal(string);
|
||||
if (value.compareTo(PercentType.HUNDRED.toBigDecimal()) > 0) {
|
||||
value = PercentType.HUNDRED.toBigDecimal();
|
||||
}
|
||||
if (value.compareTo(PercentType.ZERO.toBigDecimal()) < 0) {
|
||||
value = PercentType.ZERO.toBigDecimal();
|
||||
}
|
||||
newState = new PercentType(value);
|
||||
} catch (NumberFormatException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
state = newState;
|
||||
return newState;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.converter;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.http.internal.config.HttpChannelConfig;
|
||||
import org.openhab.binding.http.internal.transform.ValueTransformation;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link FixedValueMappingItemConverter} implements mapping conversions for different item-types
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class FixedValueMappingItemConverter extends AbstractTransformingItemConverter {
|
||||
|
||||
public FixedValueMappingItemConverter(Consumer<State> updateState, Consumer<Command> postCommand,
|
||||
@Nullable Consumer<String> sendHttpValue, ValueTransformation stateTransformations,
|
||||
ValueTransformation commandTransformations, HttpChannelConfig channelConfig) {
|
||||
super(updateState, postCommand, sendHttpValue, stateTransformations, commandTransformations, channelConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Command toCommand(String value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Command command) {
|
||||
String value = channelConfig.commandToFixedValue(command);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
"Command type '" + command.toString() + "' not supported or mapping not defined.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public State toState(String string) {
|
||||
State state = channelConfig.fixedValueToState(string);
|
||||
|
||||
return state != null ? state : UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.converter;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.http.internal.config.HttpChannelConfig;
|
||||
import org.openhab.binding.http.internal.transform.ValueTransformation;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link GenericItemConverter} implements simple conversions for different item types
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GenericItemConverter extends AbstractTransformingItemConverter {
|
||||
private final Function<String, State> toState;
|
||||
|
||||
public GenericItemConverter(Function<String, State> toState, Consumer<State> updateState,
|
||||
Consumer<Command> postCommand, @Nullable Consumer<String> sendHttpValue,
|
||||
ValueTransformation stateTransformations, ValueTransformation commandTransformations,
|
||||
HttpChannelConfig channelConfig) {
|
||||
super(updateState, postCommand, sendHttpValue, stateTransformations, commandTransformations, channelConfig);
|
||||
this.toState = toState;
|
||||
}
|
||||
|
||||
protected State toState(String value) {
|
||||
try {
|
||||
return toState.apply(value);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Command toCommand(String value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String toString(Command command) {
|
||||
return command.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.converter;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.http.internal.http.Content;
|
||||
import org.openhab.core.library.types.RawType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link ImageItemConverter} implements {@link org.openhab.core.library.items.ImageItem} conversions
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class ImageItemConverter implements ItemValueConverter {
|
||||
private final Consumer<State> updateState;
|
||||
|
||||
public ImageItemConverter(Consumer<State> updateState) {
|
||||
this.updateState = updateState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(Content content) {
|
||||
String mediaType = content.getMediaType();
|
||||
updateState.accept(
|
||||
new RawType(content.getRawContent(), mediaType != null ? mediaType : RawType.DEFAULT_MIME_TYPE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Command command) {
|
||||
throw new IllegalStateException("Read-only channel");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.converter;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.http.internal.http.Content;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* The {@link ItemValueConverter} defines the interface for converting received content to item state and converting
|
||||
* comannds to sending value
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ItemValueConverter {
|
||||
|
||||
/**
|
||||
* called to process a given content for this channel
|
||||
*
|
||||
* @param content content of the HTTP request
|
||||
*/
|
||||
void process(Content content);
|
||||
|
||||
/**
|
||||
* called to send a command to this channel
|
||||
*
|
||||
* @param command
|
||||
*/
|
||||
void send(Command command);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.converter;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.http.internal.config.HttpChannelConfig;
|
||||
import org.openhab.binding.http.internal.transform.ValueTransformation;
|
||||
import org.openhab.core.library.types.NextPreviousType;
|
||||
import org.openhab.core.library.types.PlayPauseType;
|
||||
import org.openhab.core.library.types.RewindFastforwardType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link PlayerItemConverter} implements {@link org.openhab.core.library.items.RollershutterItem}
|
||||
* conversions
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class PlayerItemConverter extends AbstractTransformingItemConverter {
|
||||
private final HttpChannelConfig channelConfig;
|
||||
|
||||
public PlayerItemConverter(Consumer<State> updateState, Consumer<Command> postCommand,
|
||||
@Nullable Consumer<String> sendHttpValue, ValueTransformation stateTransformations,
|
||||
ValueTransformation commandTransformations, HttpChannelConfig channelConfig) {
|
||||
super(updateState, postCommand, sendHttpValue, stateTransformations, commandTransformations, channelConfig);
|
||||
this.channelConfig = channelConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Command command) {
|
||||
String string = channelConfig.commandToFixedValue(command);
|
||||
if (string != null) {
|
||||
return string;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Command type '" + command.toString() + "' not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Command toCommand(String string) {
|
||||
if (string.equals(channelConfig.playValue)) {
|
||||
return PlayPauseType.PLAY;
|
||||
} else if (string.equals(channelConfig.pauseValue)) {
|
||||
return PlayPauseType.PAUSE;
|
||||
} else if (string.equals(channelConfig.nextValue)) {
|
||||
return NextPreviousType.NEXT;
|
||||
} else if (string.equals(channelConfig.previousValue)) {
|
||||
return NextPreviousType.PREVIOUS;
|
||||
} else if (string.equals(channelConfig.rewindValue)) {
|
||||
return RewindFastforwardType.REWIND;
|
||||
} else if (string.equals(channelConfig.fastforwardValue)) {
|
||||
return RewindFastforwardType.FASTFORWARD;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public State toState(String string) {
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.converter;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.http.internal.config.HttpChannelConfig;
|
||||
import org.openhab.binding.http.internal.transform.ValueTransformation;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link RollershutterItemConverter} implements {@link org.openhab.core.library.items.RollershutterItem}
|
||||
* conversions
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class RollershutterItemConverter extends AbstractTransformingItemConverter {
|
||||
private final HttpChannelConfig channelConfig;
|
||||
|
||||
public RollershutterItemConverter(Consumer<State> updateState, Consumer<Command> postCommand,
|
||||
@Nullable Consumer<String> sendHttpValue, ValueTransformation stateTransformations,
|
||||
ValueTransformation commandTransformations, HttpChannelConfig channelConfig) {
|
||||
super(updateState, postCommand, sendHttpValue, stateTransformations, commandTransformations, channelConfig);
|
||||
this.channelConfig = channelConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Command command) {
|
||||
String string = channelConfig.commandToFixedValue(command);
|
||||
if (string != null) {
|
||||
return string;
|
||||
}
|
||||
|
||||
if (command instanceof PercentType) {
|
||||
final String downValue = channelConfig.downValue;
|
||||
final String upValue = channelConfig.upValue;
|
||||
if (command.equals(PercentType.HUNDRED) && downValue != null) {
|
||||
return downValue;
|
||||
} else if (command.equals(PercentType.ZERO) && upValue != null) {
|
||||
return upValue;
|
||||
} else {
|
||||
return ((PercentType) command).toString();
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Command type '" + command.toString() + "' not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Command toCommand(String string) {
|
||||
if (string.equals(channelConfig.upValue)) {
|
||||
return UpDownType.UP;
|
||||
} else if (string.equals(channelConfig.downValue)) {
|
||||
return UpDownType.DOWN;
|
||||
} else if (string.equals(channelConfig.moveValue)) {
|
||||
return StopMoveType.MOVE;
|
||||
} else if (string.equals(channelConfig.stopValue)) {
|
||||
return StopMoveType.STOP;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public State toState(String string) {
|
||||
try {
|
||||
BigDecimal value = new BigDecimal(string);
|
||||
if (value.compareTo(PercentType.HUNDRED.toBigDecimal()) > 0) {
|
||||
return PercentType.HUNDRED;
|
||||
}
|
||||
if (value.compareTo(PercentType.ZERO.toBigDecimal()) < 0) {
|
||||
return PercentType.ZERO;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.http;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link Content} defines the pre-processed response
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Content {
|
||||
private final byte[] rawContent;
|
||||
private final Charset encoding;
|
||||
private final @Nullable String mediaType;
|
||||
|
||||
public Content(byte[] rawContent, String encoding, @Nullable String mediaType) {
|
||||
this.rawContent = rawContent;
|
||||
this.mediaType = mediaType;
|
||||
|
||||
Charset finalEncoding = StandardCharsets.UTF_8;
|
||||
try {
|
||||
finalEncoding = Charset.forName(encoding);
|
||||
} catch (IllegalArgumentException e) {
|
||||
}
|
||||
this.encoding = finalEncoding;
|
||||
}
|
||||
|
||||
public byte[] getRawContent() {
|
||||
return rawContent;
|
||||
}
|
||||
|
||||
public String getAsString() {
|
||||
return new String(rawContent, encoding);
|
||||
}
|
||||
|
||||
public @Nullable String getMediaType() {
|
||||
return mediaType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.http;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link HttpAuthException} is an exception after authorization errors
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HttpAuthException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public HttpAuthException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public HttpAuthException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.http;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link HttpResponseListener} is responsible for processing the result of a HTTP request
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HttpResponseListener extends BufferingResponseListener {
|
||||
private final Logger logger = LoggerFactory.getLogger(HttpResponseListener.class);
|
||||
private final CompletableFuture<@Nullable Content> future;
|
||||
private final String fallbackEncoding;
|
||||
|
||||
public HttpResponseListener(CompletableFuture<@Nullable Content> future) {
|
||||
this(future, null);
|
||||
}
|
||||
|
||||
public HttpResponseListener(CompletableFuture<@Nullable Content> future, @Nullable String fallbackEncoding) {
|
||||
this.future = future;
|
||||
this.fallbackEncoding = fallbackEncoding != null ? fallbackEncoding : StandardCharsets.UTF_8.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(@NonNullByDefault({}) Result result) {
|
||||
Response response = result.getResponse();
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Received from '{}': {}", result.getRequest().getURI(), responseToLogString(response));
|
||||
}
|
||||
Request request = result.getRequest();
|
||||
if (result.isFailed()) {
|
||||
logger.warn("Requesting '{}' (method='{}', content='{}') failed: {}", request.getURI(), request.getMethod(),
|
||||
request.getContent(), result.getFailure().getMessage());
|
||||
future.complete(null);
|
||||
} else {
|
||||
switch (response.getStatus()) {
|
||||
case HttpStatus.OK_200:
|
||||
byte[] content = getContent();
|
||||
String encoding = getEncoding();
|
||||
if (content != null) {
|
||||
future.complete(
|
||||
new Content(content, encoding == null ? fallbackEncoding : encoding, getMediaType()));
|
||||
} else {
|
||||
future.complete(null);
|
||||
}
|
||||
break;
|
||||
case HttpStatus.UNAUTHORIZED_401:
|
||||
logger.debug("Requesting '{}' (method='{}', content='{}') failed: Authorization error",
|
||||
request.getURI(), request.getMethod(), request.getContent());
|
||||
future.completeExceptionally(new HttpAuthException());
|
||||
break;
|
||||
default:
|
||||
logger.warn("Requesting '{}' (method='{}', content='{}') failed: {} {}", request.getURI(),
|
||||
request.getMethod(), request.getContent(), response.getStatus(), response.getReason());
|
||||
future.completeExceptionally(new IllegalStateException("Response - Code" + response.getStatus()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String responseToLogString(Response response) {
|
||||
String logString = "Code = {" + response.getStatus() + "}, Headers = {"
|
||||
+ response.getHeaders().stream().map(HttpField::toString).collect(Collectors.joining(", "))
|
||||
+ "}, Content = {" + getContentAsString() + "}";
|
||||
return logString;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.http;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.Authentication;
|
||||
import org.eclipse.jetty.client.api.AuthenticationStore;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.openhab.binding.http.internal.Util;
|
||||
import org.openhab.binding.http.internal.config.HttpThingConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link RefreshingUrlCache} is responsible for requesting from a single URL and passing the content to the
|
||||
* channels
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RefreshingUrlCache {
|
||||
private final Logger logger = LoggerFactory.getLogger(RefreshingUrlCache.class);
|
||||
|
||||
private final String url;
|
||||
private final HttpClient httpClient;
|
||||
private final int timeout;
|
||||
private final @Nullable String fallbackEncoding;
|
||||
private final Set<Consumer<Content>> consumers = ConcurrentHashMap.newKeySet();
|
||||
private final List<String> headers;
|
||||
|
||||
private final ScheduledFuture<?> future;
|
||||
private @Nullable Content lastContent;
|
||||
|
||||
public RefreshingUrlCache(ScheduledExecutorService executor, HttpClient httpClient, String url,
|
||||
HttpThingConfig thingConfig) {
|
||||
this.httpClient = httpClient;
|
||||
this.url = url;
|
||||
this.timeout = thingConfig.timeout;
|
||||
this.headers = thingConfig.headers;
|
||||
fallbackEncoding = thingConfig.encoding;
|
||||
|
||||
future = executor.scheduleWithFixedDelay(this::refresh, 0, thingConfig.refresh, TimeUnit.SECONDS);
|
||||
logger.trace("Started refresh task for URL '{}' with interval {}s", url, thingConfig.refresh);
|
||||
}
|
||||
|
||||
private void refresh() {
|
||||
refresh(false);
|
||||
}
|
||||
|
||||
private void refresh(boolean isRetry) {
|
||||
if (consumers.isEmpty()) {
|
||||
// do not refresh if we don't have listeners
|
||||
return;
|
||||
}
|
||||
|
||||
// format URL
|
||||
try {
|
||||
URI finalUrl = new URI(String.format(this.url, new Date()));
|
||||
|
||||
logger.trace("Requesting refresh (retry={}) from '{}' with timeout {}ms", isRetry, finalUrl, timeout);
|
||||
Request request = httpClient.newRequest(finalUrl).timeout(timeout, TimeUnit.MILLISECONDS);
|
||||
|
||||
headers.forEach(header -> {
|
||||
String[] keyValuePair = header.split("=", 2);
|
||||
if (keyValuePair.length == 2) {
|
||||
request.header(keyValuePair[0].trim(), keyValuePair[1].trim());
|
||||
} else {
|
||||
logger.warn("Splitting header '{}' failed. No '=' was found. Ignoring", header);
|
||||
}
|
||||
});
|
||||
|
||||
CompletableFuture<@Nullable Content> response = new CompletableFuture<>();
|
||||
response.exceptionally(e -> {
|
||||
if (e instanceof HttpAuthException) {
|
||||
if (isRetry) {
|
||||
logger.warn("Retry after authentication failure failed again for '{}', failing here",
|
||||
finalUrl);
|
||||
} else {
|
||||
AuthenticationStore authStore = httpClient.getAuthenticationStore();
|
||||
Authentication.Result authResult = authStore.findAuthenticationResult(finalUrl);
|
||||
if (authResult != null) {
|
||||
authStore.removeAuthenticationResult(authResult);
|
||||
logger.debug("Cleared authentication result for '{}', retrying immediately", finalUrl);
|
||||
refresh(true);
|
||||
} else {
|
||||
logger.warn("Could not find authentication result for '{}', failing here", finalUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}).thenAccept(this::processResult);
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Sending to '{}': {}", finalUrl, Util.requestToLogString(request));
|
||||
}
|
||||
|
||||
request.send(new HttpResponseListener(response, fallbackEncoding));
|
||||
} catch (IllegalArgumentException | URISyntaxException e) {
|
||||
logger.warn("Creating request for '{}' failed: {}", url, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
// clearing all listeners to prevent further updates
|
||||
consumers.clear();
|
||||
future.cancel(false);
|
||||
logger.trace("Stopped refresh task for URL '{}'", url);
|
||||
}
|
||||
|
||||
public void addConsumer(Consumer<Content> consumer) {
|
||||
consumers.add(consumer);
|
||||
}
|
||||
|
||||
public Optional<Content> get() {
|
||||
final Content content = lastContent;
|
||||
if (content == null) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return Optional.of(content);
|
||||
}
|
||||
}
|
||||
|
||||
private void processResult(@Nullable Content content) {
|
||||
if (content != null) {
|
||||
for (Consumer<Content> consumer : consumers) {
|
||||
try {
|
||||
consumer.accept(content);
|
||||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
logger.warn("Failed processing result for URL {}: {}", url, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
lastContent = content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.transform;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.transform.TransformationService;
|
||||
|
||||
/**
|
||||
* The {@link CascadedValueTransformationImpl} implements {@link ValueTransformation for a cascaded set of
|
||||
* transformations}
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CascadedValueTransformationImpl implements ValueTransformation {
|
||||
private final List<ValueTransformation> transformations;
|
||||
|
||||
public CascadedValueTransformationImpl(String transformationString,
|
||||
Function<String, @Nullable TransformationService> transformationServiceSupplier) {
|
||||
transformations = Arrays.stream(transformationString.split("∩")).filter(s -> !s.isEmpty())
|
||||
.map(transformation -> new SingleValueTransformation(transformation, transformationServiceSupplier))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> apply(String value) {
|
||||
Optional<String> valueOptional = Optional.of(value);
|
||||
|
||||
// process all transformations
|
||||
for (ValueTransformation transformation : transformations) {
|
||||
valueOptional = valueOptional.flatMap(transformation::apply);
|
||||
}
|
||||
|
||||
return valueOptional;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.transform;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link NoOpValueTransformation} implements a no-op (identity) transformation
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NoOpValueTransformation implements ValueTransformation {
|
||||
private static final NoOpValueTransformation NO_OP_VALUE_TRANSFORMATION = new NoOpValueTransformation();
|
||||
|
||||
@Override
|
||||
public Optional<String> apply(String value) {
|
||||
return Optional.of(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the static value transformation for identity
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static ValueTransformation getInstance() {
|
||||
return NO_OP_VALUE_TRANSFORMATION;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.transform;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.transform.TransformationException;
|
||||
import org.openhab.core.transform.TransformationService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A transformation for a value used in {@HttpChannel}.
|
||||
*
|
||||
* @author David Graeff - Initial contribution
|
||||
* @author Jan N. Klug - adapted from MQTT binding to HTTP binding
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SingleValueTransformation implements ValueTransformation {
|
||||
private final Logger logger = LoggerFactory.getLogger(SingleValueTransformation.class);
|
||||
private final Function<String, @Nullable TransformationService> transformationServiceSupplier;
|
||||
private WeakReference<@Nullable TransformationService> transformationService = new WeakReference<>(null);
|
||||
private final String pattern;
|
||||
private final String serviceName;
|
||||
|
||||
/**
|
||||
* Creates a new channel state transformer.
|
||||
*
|
||||
* @param pattern A transformation pattern, starting with the transformation service
|
||||
* name, followed by a colon and the transformation itself.
|
||||
* @param transformationServiceSupplier
|
||||
*/
|
||||
public SingleValueTransformation(String pattern,
|
||||
Function<String, @Nullable TransformationService> transformationServiceSupplier) {
|
||||
this.transformationServiceSupplier = transformationServiceSupplier;
|
||||
int index = pattern.indexOf(':');
|
||||
if (index == -1) {
|
||||
throw new IllegalArgumentException(
|
||||
"The transformation pattern must consist of the type and the pattern separated by a colon");
|
||||
}
|
||||
this.serviceName = pattern.substring(0, index).toUpperCase();
|
||||
this.pattern = pattern.substring(index + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> apply(String value) {
|
||||
TransformationService transformationService = this.transformationService.get();
|
||||
if (transformationService == null) {
|
||||
transformationService = transformationServiceSupplier.apply(serviceName);
|
||||
if (transformationService == null) {
|
||||
logger.warn("Transformation service {} for pattern {} not found!", serviceName, pattern);
|
||||
return Optional.empty();
|
||||
}
|
||||
this.transformationService = new WeakReference<>(transformationService);
|
||||
}
|
||||
|
||||
try {
|
||||
String result = transformationService.transform(pattern, value);
|
||||
if (result == null) {
|
||||
logger.debug("Transformation {} returned empty result when applied to {}.", this, value);
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(result);
|
||||
} catch (TransformationException e) {
|
||||
logger.warn("Executing transformation {} failed: {}", this, e.getMessage());
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChannelStateTransformation{pattern='" + pattern + "', serviceName='" + serviceName + "'}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.transform;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link ValueTransformation} applies a set of transformations to a value
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ValueTransformation {
|
||||
|
||||
/**
|
||||
* applies the value transformation to a value
|
||||
*
|
||||
* @param value The value
|
||||
* @return Optional of string representing the transformed value (empty if transformation not present or failed)
|
||||
*/
|
||||
Optional<String> apply(String value);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.http.internal.transform;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link ValueTransformationProvider} allows to retrieve a transformation service by name
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ValueTransformationProvider {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param pattern A transformation pattern, starting with the transformation service
|
||||
* * name, followed by a colon and the transformation itself.
|
||||
* @return
|
||||
*/
|
||||
ValueTransformation getValueTransformation(@Nullable String pattern);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="http" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>HTTP Binding</name>
|
||||
<description>This is the binding for retrieving and processing HTTP resources.</description>
|
||||
<author>Jan N. Klug</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,350 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="channel-type:http:channel-config">
|
||||
<parameter name="stateExtension" type="text">
|
||||
<label>State URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="commandExtension" type="text">
|
||||
<label>Command URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for sending values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="stateTransformation" type="text">
|
||||
<label>State Transformation</label>
|
||||
<description>Transformation pattern used when receiving values.</description>
|
||||
</parameter>
|
||||
<parameter name="commandTransformation" type="text">
|
||||
<label>Command Transformation</label>
|
||||
<description>Transformation pattern used when sending values.</description>
|
||||
</parameter>
|
||||
<parameter name="mode" type="text">
|
||||
<label>Read/Write Mode</label>
|
||||
<options>
|
||||
<option value="READWRITE">Read/Write</option>
|
||||
<option value="READONLY">Read Only</option>
|
||||
<option value="WRITEONLY">Write Only</option>
|
||||
</options>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<advanced>true</advanced>
|
||||
<default>READWRITE</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="channel-type:http:channel-config-color">
|
||||
<parameter name="stateExtension" type="text">
|
||||
<label>State URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="commandExtension" type="text">
|
||||
<label>Command URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for sending values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="stateTransformation" type="text">
|
||||
<label>State Transformation</label>
|
||||
<description>Transformation pattern used when receiving values.</description>
|
||||
</parameter>
|
||||
<parameter name="commandTransformation" type="text">
|
||||
<label>Command Transformation</label>
|
||||
<description>Transformation pattern used when sending values.</description>
|
||||
</parameter>
|
||||
<parameter name="onValue" type="text">
|
||||
<label>On Value</label>
|
||||
<description>The value that represents ON</description>
|
||||
</parameter>
|
||||
<parameter name="offValue" type="text">
|
||||
<label>Off Value</label>
|
||||
<description>The value that represents OFF</description>
|
||||
</parameter>
|
||||
<parameter name="increaseValue" type="text">
|
||||
<label>Increase Value</label>
|
||||
<description>The value that represents INCREASE</description>
|
||||
</parameter>
|
||||
<parameter name="decreaseValue" type="text">
|
||||
<label>Decrease Value</label>
|
||||
<description>The value that represents DECREASE</description>
|
||||
</parameter>
|
||||
<parameter name="step" type="text">
|
||||
<label>Increase/Decrease Step</label>
|
||||
<description>The value by which the current brightness is increased/decreased if the corresponding command is
|
||||
received</description>
|
||||
<default>1</default>
|
||||
</parameter>
|
||||
<parameter name="colorMode" type="text">
|
||||
<label>Color Mode</label>
|
||||
<description>Color mode for parsing incoming and sending outgoing values</description>
|
||||
<options>
|
||||
<option value="HSB">HSB</option>
|
||||
<option value="RGB">RGB</option>
|
||||
</options>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<default>RGB</default>
|
||||
</parameter>
|
||||
<parameter name="mode" type="text">
|
||||
<label>Read/Write Mode</label>
|
||||
<options>
|
||||
<option value="READWRITE">Read/Write</option>
|
||||
<option value="READONLY">Read Only</option>
|
||||
<option value="WRITEONLY">Write Only</option>
|
||||
</options>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<advanced>true</advanced>
|
||||
<default>READWRITE</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="channel-type:http:channel-config-contact">
|
||||
<parameter name="stateExtension" type="text">
|
||||
<label>State URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="commandExtension" type="text">
|
||||
<label>Command URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for sending values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="stateTransformation" type="text">
|
||||
<label>State Transformation</label>
|
||||
<description>Transformation pattern used when receiving values.</description>
|
||||
</parameter>
|
||||
<parameter name="commandTransformation" type="text">
|
||||
<label>Command Transformation</label>
|
||||
<description>Transformation pattern used when sending values.</description>
|
||||
</parameter>
|
||||
<parameter name="openValue" type="text" required="true">
|
||||
<label>Open Value</label>
|
||||
<description>The value that represents OPEN</description>
|
||||
</parameter>
|
||||
<parameter name="closedValue" type="text" required="true">
|
||||
<label>Closed Value</label>
|
||||
<description>The value that represents CLOSED</description>
|
||||
</parameter>
|
||||
<parameter name="mode" type="text">
|
||||
<label>Read/Write Mode</label>
|
||||
<options>
|
||||
<option value="READWRITE">Read/Write</option>
|
||||
<option value="READONLY">Read Only</option>
|
||||
<option value="WRITEONLY">Write Only</option>
|
||||
</options>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<advanced>true</advanced>
|
||||
<default>READWRITE</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="channel-type:http:channel-config-dimmer">
|
||||
<parameter name="stateExtension" type="text">
|
||||
<label>State URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="commandExtension" type="text">
|
||||
<label>Command URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for sending values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="stateTransformation" type="text">
|
||||
<label>State Transformation</label>
|
||||
<description>Transformation pattern used when receiving values.</description>
|
||||
</parameter>
|
||||
<parameter name="commandTransformation" type="text">
|
||||
<label>Command Transformation</label>
|
||||
<description>Transformation pattern used when sending values.</description>
|
||||
</parameter>
|
||||
<parameter name="onValue" type="text">
|
||||
<label>On Value</label>
|
||||
<description>The value that represents ON</description>
|
||||
</parameter>
|
||||
<parameter name="offValue" type="text">
|
||||
<label>Off Value</label>
|
||||
<description>The value that represents OFF</description>
|
||||
</parameter>
|
||||
<parameter name="increaseValue" type="text">
|
||||
<label>Increase Value</label>
|
||||
<description>The value that represents INCREASE</description>
|
||||
</parameter>
|
||||
<parameter name="decreaseValue" type="text">
|
||||
<label>Decrease Value</label>
|
||||
<description>The value that represents DECREASE</description>
|
||||
</parameter>
|
||||
<parameter name="step" type="text">
|
||||
<label>Increase/Decrease Step</label>
|
||||
<description>The value by which the current brightness is increased/decreased if the corresponding command is
|
||||
received</description>
|
||||
<default>1</default>
|
||||
</parameter>
|
||||
<parameter name="mode" type="text">
|
||||
<label>Read/Write Mode</label>
|
||||
<options>
|
||||
<option value="READWRITE">Read/Write</option>
|
||||
<option value="READONLY">Read Only</option>
|
||||
<option value="WRITEONLY">Write Only</option>
|
||||
</options>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<advanced>true</advanced>
|
||||
<default>READWRITE</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="channel-type:http:channel-config-image">
|
||||
<parameter name="stateExtension" type="text">
|
||||
<label>State URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="channel-type:http:channel-config-player">
|
||||
<parameter name="stateExtension" type="text">
|
||||
<label>State URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="commandExtension" type="text">
|
||||
<label>Command URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for sending values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="stateTransformation" type="text">
|
||||
<label>State Transformation</label>
|
||||
<description>Transformation pattern used when receiving values.</description>
|
||||
</parameter>
|
||||
<parameter name="commandTransformation" type="text">
|
||||
<label>Command Transformation</label>
|
||||
<description>Transformation pattern used when sending values.</description>
|
||||
</parameter>
|
||||
|
||||
<parameter name="playValue" type="text">
|
||||
<label>Play Value</label>
|
||||
<description>The value that represents PLAY</description>
|
||||
</parameter>
|
||||
<parameter name="pauseValue" type="text">
|
||||
<label>Pause Value</label>
|
||||
<description>The value that represents PAUSE</description>
|
||||
</parameter>
|
||||
<parameter name="nextValue" type="text">
|
||||
<label>Next Value</label>
|
||||
<description>The value that represents NEXT</description>
|
||||
</parameter>
|
||||
<parameter name="previousValue" type="text">
|
||||
<label>Previous Value</label>
|
||||
<description>The value that represents PREVIOUS</description>
|
||||
</parameter>
|
||||
<parameter name="rewindValue" type="text">
|
||||
<label>Rewind Value</label>
|
||||
<description>The value that represents REWIND</description>
|
||||
</parameter>
|
||||
<parameter name="fastforwardValue" type="text">
|
||||
<label>Fast Forward Value</label>
|
||||
<description>The value that represents FASTFORWARD</description>
|
||||
</parameter>
|
||||
<parameter name="mode" type="text">
|
||||
<label>Read/Write Mode</label>
|
||||
<options>
|
||||
<option value="READWRITE">Read/Write</option>
|
||||
<option value="READONLY">Read Only</option>
|
||||
<option value="WRITEONLY">Write Only</option>
|
||||
</options>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<advanced>true</advanced>
|
||||
<default>READWRITE</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="channel-type:http:channel-config-rollershutter">
|
||||
<parameter name="stateExtension" type="text">
|
||||
<label>State URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="commandExtension" type="text">
|
||||
<label>Command URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for sending values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="stateTransformation" type="text">
|
||||
<label>State Transformation</label>
|
||||
<description>Transformation pattern used when receiving values.</description>
|
||||
</parameter>
|
||||
<parameter name="commandTransformation" type="text">
|
||||
<label>Command Transformation</label>
|
||||
<description>Transformation pattern used when sending values.</description>
|
||||
</parameter>
|
||||
<parameter name="upValue" type="text">
|
||||
<label>Up Value</label>
|
||||
<description>The value that represents UP</description>
|
||||
</parameter>
|
||||
<parameter name="downValue" type="text">
|
||||
<label>Down Value</label>
|
||||
<description>The value that represents DOWN</description>
|
||||
</parameter>
|
||||
<parameter name="stopValue" type="text">
|
||||
<label>Stop Value</label>
|
||||
<description>The value that represents STOP</description>
|
||||
</parameter>
|
||||
<parameter name="moveValue" type="text">
|
||||
<label>Move Value</label>
|
||||
<description>The value that represents MOVE</description>
|
||||
</parameter>
|
||||
<parameter name="mode" type="text">
|
||||
<label>Read/Write Mode</label>
|
||||
<options>
|
||||
<option value="READWRITE">Read/Write</option>
|
||||
<option value="READONLY">Read Only</option>
|
||||
<option value="WRITEONLY">Write Only</option>
|
||||
</options>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<advanced>true</advanced>
|
||||
<default>READWRITE</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="channel-type:http:channel-config-switch">
|
||||
<parameter name="stateExtension" type="text">
|
||||
<label>State URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for retrieving values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="commandExtension" type="text">
|
||||
<label>Command URL Extension</label>
|
||||
<description>This value is added to the base URL configured in the thing for sending values.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="stateTransformation" type="text">
|
||||
<label>State Transformation</label>
|
||||
<description>Transformation pattern used when receiving values.</description>
|
||||
</parameter>
|
||||
<parameter name="commandTransformation" type="text">
|
||||
<label>Command Transformation</label>
|
||||
<description>Transformation pattern used when sending values.</description>
|
||||
</parameter>
|
||||
<parameter name="onValue" type="text" required="true">
|
||||
<label>On Value</label>
|
||||
<description>The value that represents ON</description>
|
||||
</parameter>
|
||||
<parameter name="offValue" type="text" required="true">
|
||||
<label>Off Value</label>
|
||||
<description>The value that represents OFF</description>
|
||||
</parameter>
|
||||
<parameter name="mode" type="text">
|
||||
<label>Read/Write Mode</label>
|
||||
<options>
|
||||
<option value="READWRITE">Read/Write</option>
|
||||
<option value="READONLY">Read Only</option>
|
||||
<option value="WRITEONLY">Write Only</option>
|
||||
</options>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<advanced>true</advanced>
|
||||
<default>READWRITE</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,158 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="http"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="url"
|
||||
extensible="color,contact,datetime,dimmer,image,location,number,rollershutter,string,switch">
|
||||
<label>HTTP URL Thing</label>
|
||||
<description>Represents a base URL and all associated requests.</description>
|
||||
|
||||
<config-description>
|
||||
<parameter name="baseURL" type="text" required="true">
|
||||
<label>Base URL</label>
|
||||
<description>The URL set here can be extended in the channel configuration.</description>
|
||||
<context>url</context>
|
||||
</parameter>
|
||||
<parameter name="refresh" type="integer" unit="s" min="1">
|
||||
<label>Refresh Time</label>
|
||||
<description>Time between two refreshes of all channels</description>
|
||||
<default>30</default>
|
||||
</parameter>
|
||||
<parameter name="timeout" type="integer" unit="ms" min="0">
|
||||
<label>Timeout</label>
|
||||
<description>The timeout in ms for each request</description>
|
||||
<default>3000</default>
|
||||
</parameter>
|
||||
<parameter name="username" type="text">
|
||||
<label>Username</label>
|
||||
<description>Basic Authentication username</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="password" type="text">
|
||||
<label>Password</label>
|
||||
<description>Basic Authentication password</description>
|
||||
<context>password</context>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="authMode" type="text">
|
||||
<label>Authentication Mode</label>
|
||||
<options>
|
||||
<option value="BASIC">Basic Authentication</option>
|
||||
<option value="DIGEST">Digest Authentication</option>
|
||||
</options>
|
||||
<default>BASIC</default>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="commandMethod" type="text">
|
||||
<label>Command Method</label>
|
||||
<description>HTTP method (GET,POST, PUT) for sending commands.</description>
|
||||
<options>
|
||||
<option value="GET">GET</option>
|
||||
<option value="POST">POST</option>
|
||||
<option value="PUT">PUT</option>
|
||||
</options>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<default>GET</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="contentType" type="text">
|
||||
<label>Content Type</label>
|
||||
<description>The MIME content type. Only used for `POST` and `PUT`.</description>
|
||||
<options>
|
||||
<option value="application/json">application/json</option>
|
||||
<option value="application/xml">application/xml</option>
|
||||
<option value="text/html">text/html</option>
|
||||
<option value="text/plain">text/plain</option>
|
||||
<option value="text/xml">text/xml</option>
|
||||
</options>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="encoding" type="text">
|
||||
<label>Fallback Encoding</label>
|
||||
<description>Fallback Encoding text received by this thing's channels.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="headers" type="text" multiple="true">
|
||||
<label>Headers</label>
|
||||
<description>Additional headers send along with the request</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="ignoreSSLErrors" type="boolean">
|
||||
<label>Ignore SSL Errors</label>
|
||||
<description>If set to true ignores invalid SSL certificate errors. This is potentially dangerous.</description>
|
||||
<default>false</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="color">
|
||||
<item-type>Color</item-type>
|
||||
<label>Color Channel</label>
|
||||
<config-description-ref uri="channel-type:http:channel-config-color"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="contact">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Contact Channel</label>
|
||||
<config-description-ref uri="channel-type:http:channel-config-contact"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="datetime">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>DateTime Channel</label>
|
||||
<config-description-ref uri="channel-type:http:channel-config"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dimmer">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Dimmer Channel</label>
|
||||
<config-description-ref uri="channel-type:http:channel-config-dimmer"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="image">
|
||||
<item-type>Image</item-type>
|
||||
<label>Image Channel</label>
|
||||
<config-description-ref uri="channel-type:http:channel-config-image"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="location">
|
||||
<item-type>Location</item-type>
|
||||
<label>Location Channel</label>
|
||||
<config-description-ref uri="channel-type:http:channel-config"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="number">
|
||||
<item-type>Number</item-type>
|
||||
<label>Number Channel</label>
|
||||
<config-description-ref uri="channel-type:http:channel-config"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="player">
|
||||
<item-type>Player</item-type>
|
||||
<label>Player Channel</label>
|
||||
<config-description-ref uri="channel-type:http:channel-config-player"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rollershutter">
|
||||
<item-type>Rollershutter</item-type>
|
||||
<label>Rollershutter Channel</label>
|
||||
<config-description-ref uri="channel-type:http:channel-config-rollershutter"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="string">
|
||||
<item-type>String</item-type>
|
||||
<label>String Channel</label>
|
||||
<config-description-ref uri="channel-type:http:channel-config"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="switch">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Switch Channel</label>
|
||||
<config-description-ref uri="channel-type:http:channel-config-switch"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user