Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for things with generic channels #3355

Merged
merged 4 commits into from Jun 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions bundles/org.openhab.core.thing/pom.xml
Expand Up @@ -25,6 +25,11 @@
<artifactId>org.openhab.core.io.console</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.transform</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.test</artifactId>
Expand Down
@@ -0,0 +1,43 @@
/**
* Copyright (c) 2010-2023 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.core.thing.binding.generic;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.types.Command;

/**
* The {@link ChannelHandler} defines the interface for converting received {@link ChannelHandlerContent}
* to {@link org.openhab.core.types.State}s for posting updates to {@link org.openhab.core.thing.Channel}s and
* {@link Command}s to values for sending
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public interface ChannelHandler {

/**
* called to process a given content for this channel
*
* @param content raw content to process (<code>null</code> results in
* {@link org.openhab.core.types.UnDefType#UNDEF})
*/
void process(@Nullable ChannelHandlerContent content);

/**
* called to send a command to this channel
*
* @param command
*/
void send(Command command);
}
@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2023 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.core.thing.binding.generic;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

/**
* The {@link ChannelHandlerContent} defines the pre-processed response
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class ChannelHandlerContent {
private final byte[] rawContent;
private final Charset encoding;
private final @Nullable String mediaType;

public ChannelHandlerContent(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,27 @@
/**
* Copyright (c) 2010-2023 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.core.thing.binding.generic;

import org.eclipse.jdt.annotation.NonNullByDefault;

/**
* The {@link ChannelMode} enum defines control modes for channels
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public enum ChannelMode {
READONLY,
READWRITE,
WRITEONLY
}
@@ -0,0 +1,97 @@
/**
* Copyright (c) 2010-2023 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.core.thing.binding.generic;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.transform.TransformationException;
import org.openhab.core.transform.TransformationHelper;
import org.openhab.core.transform.TransformationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The {@link ChannelTransformation} can be used to transform an input value using one or more transformations.
* Individual transformations can be chained with <code>∩</code> and must follow the pattern
* <code>serviceName:function</code> where <code>serviceName</code> refers to a {@link TransformationService} and
* <code>function</code> has to be a valid transformation function for this service
*
* @author Jan N. Klug - Initial contribution
*/
public class ChannelTransformation {
private final Logger logger = LoggerFactory.getLogger(ChannelTransformation.class);
private List<TransformationStep> transformationSteps;

public ChannelTransformation(@Nullable String transformationString) {
if (transformationString != null) {
try {
transformationSteps = Arrays.stream(transformationString.split("∩")).filter(s -> !s.isBlank())
.map(TransformationStep::new).toList();
return;
} catch (IllegalArgumentException e) {
logger.warn("Transformation ignored, failed to parse {}: {}", transformationString, e.getMessage());
}
}
transformationSteps = List.of();
}

public Optional<String> apply(String value) {
Optional<String> valueOptional = Optional.of(value);

// process all transformations
for (TransformationStep transformationStep : transformationSteps) {
valueOptional = valueOptional.flatMap(transformationStep::apply);
}

logger.trace("Transformed '{}' to '{}' using '{}'", value, valueOptional, transformationSteps);
return valueOptional;
}

private static class TransformationStep {
private final Logger logger = LoggerFactory.getLogger(TransformationStep.class);
private final String serviceName;
private final String function;

public TransformationStep(String pattern) throws IllegalArgumentException {
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().trim();
this.function = pattern.substring(index + 1).trim();
}

public Optional<String> apply(String value) {
TransformationService service = TransformationHelper.getTransformationService(serviceName);
if (service != null) {
try {
return Optional.ofNullable(service.transform(function, value));
} catch (TransformationException e) {
logger.debug("Applying {} failed: {}", this, e.getMessage());
}
} else {
logger.warn("Failed to use {}, service not found", this);
}
return Optional.empty();
}

@Override
public String toString() {
return "TransformationStep{serviceName='" + serviceName + "', function='" + function + "'}";
}
}
}
@@ -0,0 +1,138 @@
/**
* Copyright (c) 2010-2023 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.core.thing.binding.generic;

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.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.thing.binding.generic.converter.ColorChannelHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;

/**
* The {@link ChannelValueConverterConfig} is a base class for the channel configuration of things
* using the {@link ChannelHandler}s
*
* @author Jan N. Klug - Initial contribution
*/

@NonNullByDefault
public class ChannelValueConverterConfig {
private final Map<String, State> stringStateMap = new HashMap<>();
private final Map<Command, @Nullable String> commandStringMap = new HashMap<>();

public ChannelMode mode = ChannelMode.READWRITE;

// number
public @Nullable String unit;

// 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 ColorChannelHandler.ColorMode colorMode = ColorChannelHandler.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;

private boolean initialized = false;

/**
* 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);
}
}
}