Add support for things with generic channels (#3355)
* Add support for generic channels Signed-off-by: Jan N. Klug <github@klug.nrw>pull/3674/head
parent
64fd046266
commit
78e66745ab
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
/**
|
||||
* 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.converter;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Optional;
|
||||
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.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.thing.binding.generic.ChannelTransformation;
|
||||
import org.openhab.core.thing.binding.generic.ChannelValueConverterConfig;
|
||||
import org.openhab.core.thing.internal.binding.generic.converter.AbstractTransformingChannelHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link ColorChannelHandler} implements {@link org.openhab.core.library.items.ColorItem} conversions
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class ColorChannelHandler extends AbstractTransformingChannelHandler {
|
||||
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("(?<r>\\d+),(?<g>\\d+),(?<b>\\d+)");
|
||||
|
||||
private State state = UnDefType.UNDEF;
|
||||
|
||||
public ColorChannelHandler(Consumer<State> updateState, Consumer<Command> postCommand,
|
||||
@Nullable Consumer<String> sendValue, ChannelTransformation stateTransformations,
|
||||
ChannelTransformation commandTransformations, ChannelValueConverterConfig channelConfig) {
|
||||
super(updateState, postCommand, sendValue, stateTransformations, commandTransformations, 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 newState) {
|
||||
state = newState;
|
||||
return hsbToString(newState);
|
||||
} else if (command instanceof PercentType percentCommand && state instanceof HSBType colorState) {
|
||||
HSBType newState = new HSBType(colorState.getBrightness(), colorState.getSaturation(), percentCommand);
|
||||
state = newState;
|
||||
return hsbToString(newState);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Command type '" + command.toString() + "' not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<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("r"));
|
||||
int g = Integer.parseInt(matcher.group("g"));
|
||||
int b = Integer.parseInt(matcher.group("b"));
|
||||
newState = HSBType.fromRGB(r, g, b);
|
||||
}
|
||||
case HSB -> newState = new HSBType(string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state = newState;
|
||||
return Optional.of(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,104 @@
|
|||
/**
|
||||
* 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.converter;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.thing.binding.generic.ChannelTransformation;
|
||||
import org.openhab.core.thing.binding.generic.ChannelValueConverterConfig;
|
||||
import org.openhab.core.thing.internal.binding.generic.converter.AbstractTransformingChannelHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link DimmerChannelHandler} implements {@link org.openhab.core.library.items.DimmerItem} conversions
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class DimmerChannelHandler extends AbstractTransformingChannelHandler {
|
||||
private static final BigDecimal HUNDRED = BigDecimal.valueOf(100);
|
||||
|
||||
private State state = UnDefType.UNDEF;
|
||||
|
||||
public DimmerChannelHandler(Consumer<State> updateState, Consumer<Command> postCommand,
|
||||
@Nullable Consumer<String> sendValue, ChannelTransformation stateTransformations,
|
||||
ChannelTransformation commandTransformations, ChannelValueConverterConfig channelConfig) {
|
||||
super(updateState, postCommand, sendValue, stateTransformations, commandTransformations, 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 Optional<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 Optional.of(newState);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* 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.converter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.binding.generic.ChannelTransformation;
|
||||
import org.openhab.core.thing.binding.generic.ChannelValueConverterConfig;
|
||||
import org.openhab.core.thing.internal.binding.generic.converter.AbstractTransformingChannelHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link FixedValueMappingChannelHandler} implements mapping conversions for different item-types
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class FixedValueMappingChannelHandler extends AbstractTransformingChannelHandler {
|
||||
|
||||
public FixedValueMappingChannelHandler(Consumer<State> updateState, Consumer<Command> postCommand,
|
||||
@Nullable Consumer<String> sendValue, ChannelTransformation stateTransformations,
|
||||
ChannelTransformation commandTransformations, ChannelValueConverterConfig channelConfig) {
|
||||
super(updateState, postCommand, sendValue, 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 Optional<State> toState(String string) {
|
||||
State state = channelConfig.fixedValueToState(string);
|
||||
|
||||
return Optional.of(Objects.requireNonNullElse(state, UnDefType.UNDEF));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/**
|
||||
* 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.converter;
|
||||
|
||||
import java.util.Optional;
|
||||
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.core.thing.binding.generic.ChannelTransformation;
|
||||
import org.openhab.core.thing.binding.generic.ChannelValueConverterConfig;
|
||||
import org.openhab.core.thing.internal.binding.generic.converter.AbstractTransformingChannelHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link GenericChannelHandler} implements simple conversions for different item types
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GenericChannelHandler extends AbstractTransformingChannelHandler {
|
||||
private final Function<String, State> toState;
|
||||
|
||||
public GenericChannelHandler(Function<String, State> toState, Consumer<State> updateState,
|
||||
Consumer<Command> postCommand, @Nullable Consumer<String> sendValue,
|
||||
ChannelTransformation stateTransformations, ChannelTransformation commandTransformations,
|
||||
ChannelValueConverterConfig channelConfig) {
|
||||
super(updateState, postCommand, sendValue, stateTransformations, commandTransformations, channelConfig);
|
||||
this.toState = toState;
|
||||
}
|
||||
|
||||
protected Optional<State> toState(String value) {
|
||||
try {
|
||||
return Optional.of(toState.apply(value));
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Optional.of(UnDefType.UNDEF);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Command toCommand(String value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String toString(Command command) {
|
||||
return command.toString();
|
||||
}
|
||||
}
|
|
@ -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.converter;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.types.RawType;
|
||||
import org.openhab.core.thing.binding.generic.ChannelHandler;
|
||||
import org.openhab.core.thing.binding.generic.ChannelHandlerContent;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link ImageChannelHandler} implements {@link org.openhab.core.library.items.ImageItem} conversions
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class ImageChannelHandler implements ChannelHandler {
|
||||
private final Consumer<State> updateState;
|
||||
|
||||
public ImageChannelHandler(Consumer<State> updateState) {
|
||||
this.updateState = updateState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(@Nullable ChannelHandlerContent content) {
|
||||
if (content == null) {
|
||||
updateState.accept(UnDefType.UNDEF);
|
||||
return;
|
||||
}
|
||||
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,79 @@
|
|||
/**
|
||||
* 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.converter;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.measure.format.MeasurementParseException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.thing.binding.generic.ChannelTransformation;
|
||||
import org.openhab.core.thing.binding.generic.ChannelValueConverterConfig;
|
||||
import org.openhab.core.thing.internal.binding.generic.converter.AbstractTransformingChannelHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link NumberChannelHandler} implements {@link org.openhab.core.library.items.NumberItem} conversions
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NumberChannelHandler extends AbstractTransformingChannelHandler {
|
||||
|
||||
public NumberChannelHandler(Consumer<State> updateState, Consumer<Command> postCommand,
|
||||
@Nullable Consumer<String> sendValue, ChannelTransformation stateTransformations,
|
||||
ChannelTransformation commandTransformations, ChannelValueConverterConfig channelConfig) {
|
||||
super(updateState, postCommand, sendValue, stateTransformations, commandTransformations, channelConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Command toCommand(String value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<State> toState(String value) {
|
||||
String trimmedValue = value.trim();
|
||||
State newState = UnDefType.UNDEF;
|
||||
if (!trimmedValue.isEmpty()) {
|
||||
try {
|
||||
if (channelConfig.unit != null) {
|
||||
// we have a given unit - use that
|
||||
newState = new QuantityType<>(trimmedValue + " " + channelConfig.unit);
|
||||
} else {
|
||||
try {
|
||||
// try if we have a simple number
|
||||
newState = new DecimalType(trimmedValue);
|
||||
} catch (IllegalArgumentException e1) {
|
||||
// not a plain number, maybe with unit?
|
||||
newState = new QuantityType<>(trimmedValue);
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException | MeasurementParseException e) {
|
||||
// finally failed
|
||||
}
|
||||
}
|
||||
return Optional.of(newState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String toString(Command command) {
|
||||
return command.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/**
|
||||
* 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.converter;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
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.thing.binding.generic.ChannelTransformation;
|
||||
import org.openhab.core.thing.binding.generic.ChannelValueConverterConfig;
|
||||
import org.openhab.core.thing.internal.binding.generic.converter.AbstractTransformingChannelHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link PlayerChannelHandler} implements {@link org.openhab.core.library.items.RollershutterItem}
|
||||
* conversions
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class PlayerChannelHandler extends AbstractTransformingChannelHandler {
|
||||
private @Nullable String lastCommand; // store last command to prevent duplicate commands
|
||||
|
||||
public PlayerChannelHandler(Consumer<State> updateState, Consumer<Command> postCommand,
|
||||
@Nullable Consumer<String> sendValue, ChannelTransformation stateTransformations,
|
||||
ChannelTransformation commandTransformations, ChannelValueConverterConfig channelConfig) {
|
||||
super(updateState, postCommand, sendValue, stateTransformations, commandTransformations, 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(lastCommand)) {
|
||||
// only send commands once
|
||||
return null;
|
||||
}
|
||||
lastCommand = 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 Optional<State> toState(String string) {
|
||||
// no value - we ignore state updates
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
* 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.converter;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
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.thing.binding.generic.ChannelTransformation;
|
||||
import org.openhab.core.thing.binding.generic.ChannelValueConverterConfig;
|
||||
import org.openhab.core.thing.internal.binding.generic.converter.AbstractTransformingChannelHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link RollershutterChannelHandler} implements {@link org.openhab.core.library.items.RollershutterItem}
|
||||
* conversions
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class RollershutterChannelHandler extends AbstractTransformingChannelHandler {
|
||||
|
||||
public RollershutterChannelHandler(Consumer<State> updateState, Consumer<Command> postCommand,
|
||||
@Nullable Consumer<String> sendValue, ChannelTransformation stateTransformations,
|
||||
ChannelTransformation commandTransformations, ChannelValueConverterConfig channelConfig) {
|
||||
super(updateState, postCommand, sendValue, stateTransformations, commandTransformations, 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 Optional<State> toState(String string) {
|
||||
State newState = UnDefType.UNDEF;
|
||||
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
|
||||
}
|
||||
|
||||
return Optional.of(newState);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
/**
|
||||
* 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.internal.binding.generic.converter;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.binding.generic.ChannelHandler;
|
||||
import org.openhab.core.thing.binding.generic.ChannelHandlerContent;
|
||||
import org.openhab.core.thing.binding.generic.ChannelMode;
|
||||
import org.openhab.core.thing.binding.generic.ChannelTransformation;
|
||||
import org.openhab.core.thing.binding.generic.ChannelValueConverterConfig;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link AbstractTransformingChannelHandler} is a base class for an item converter with transformations
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class AbstractTransformingChannelHandler implements ChannelHandler {
|
||||
private final Consumer<State> updateState;
|
||||
private final Consumer<Command> postCommand;
|
||||
private final @Nullable Consumer<String> sendValue;
|
||||
private final ChannelTransformation stateTransformations;
|
||||
private final ChannelTransformation commandTransformations;
|
||||
|
||||
protected final ChannelValueConverterConfig channelConfig;
|
||||
|
||||
public AbstractTransformingChannelHandler(Consumer<State> updateState, Consumer<Command> postCommand,
|
||||
@Nullable Consumer<String> sendValue, ChannelTransformation stateTransformations,
|
||||
ChannelTransformation commandTransformations, ChannelValueConverterConfig channelConfig) {
|
||||
this.updateState = updateState;
|
||||
this.postCommand = postCommand;
|
||||
this.sendValue = sendValue;
|
||||
this.stateTransformations = stateTransformations;
|
||||
this.commandTransformations = commandTransformations;
|
||||
this.channelConfig = channelConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(@Nullable ChannelHandlerContent content) {
|
||||
if (content == null) {
|
||||
updateState.accept(UnDefType.UNDEF);
|
||||
return;
|
||||
}
|
||||
if (channelConfig.mode != ChannelMode.WRITEONLY) {
|
||||
stateTransformations.apply(content.getAsString()).ifPresent(transformedValue -> {
|
||||
Command command = toCommand(transformedValue);
|
||||
if (command != null) {
|
||||
postCommand.accept(command);
|
||||
} else {
|
||||
toState(transformedValue).ifPresent(updateState);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw new IllegalStateException("Write-only channel");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Command command) {
|
||||
Consumer<String> sendHttpValue = this.sendValue;
|
||||
if (sendHttpValue != null && channelConfig.mode != ChannelMode.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 Optional<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 {
|
||||
ChannelHandler create(Consumer<State> updateState, Consumer<Command> postCommand,
|
||||
@Nullable Consumer<String> sendHttpValue, ChannelTransformation stateTransformations,
|
||||
ChannelTransformation commandTransformations, ChannelValueConverterConfig channelConfig);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
/**
|
||||
* 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 static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.openhab.core.transform.TransformationException;
|
||||
import org.openhab.core.transform.TransformationHelper;
|
||||
import org.openhab.core.transform.TransformationService;
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.osgi.framework.ServiceReference;
|
||||
|
||||
/**
|
||||
* The {@link ChannelTransformationTest} contains tests for the {@link ChannelTransformation}
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@NonNullByDefault
|
||||
public class ChannelTransformationTest {
|
||||
private static final String T1_NAME = "TRANSFORM1";
|
||||
private static final String T1_PATTERN = "T1Pattern";
|
||||
private static final String T1_INPUT = "T1Input";
|
||||
private static final String T1_RESULT = "T1Result";
|
||||
|
||||
private static final String T2_NAME = "TRANSFORM2";
|
||||
private static final String T2_PATTERN = "T2Pattern";
|
||||
private static final String T2_INPUT = T1_RESULT;
|
||||
private static final String T2_RESULT = "T2Result";
|
||||
|
||||
private @Mock @NonNullByDefault({}) TransformationService transformationService1Mock;
|
||||
private @Mock @NonNullByDefault({}) TransformationService transformationService2Mock;
|
||||
|
||||
private @Mock @NonNullByDefault({}) BundleContext bundleContextMock;
|
||||
private @Mock @NonNullByDefault({}) ServiceReference<TransformationService> serviceRef1Mock;
|
||||
private @Mock @NonNullByDefault({}) ServiceReference<TransformationService> serviceRef2Mock;
|
||||
|
||||
private @NonNullByDefault({}) TransformationHelper transformationHelper;
|
||||
|
||||
@BeforeEach
|
||||
public void init() throws TransformationException {
|
||||
Mockito.when(transformationService1Mock.transform(eq(T1_PATTERN), eq(T1_INPUT)))
|
||||
.thenAnswer(answer -> T1_RESULT);
|
||||
Mockito.when(transformationService2Mock.transform(eq(T2_PATTERN), eq(T1_INPUT)))
|
||||
.thenAnswer(answer -> T2_RESULT);
|
||||
Mockito.when(transformationService2Mock.transform(eq(T2_PATTERN), eq(T2_INPUT)))
|
||||
.thenAnswer(answer -> T2_RESULT);
|
||||
|
||||
Mockito.when(serviceRef1Mock.getProperty(any())).thenReturn("TRANSFORM1");
|
||||
Mockito.when(serviceRef2Mock.getProperty(any())).thenReturn("TRANSFORM2");
|
||||
|
||||
Mockito.when(bundleContextMock.getService(serviceRef1Mock)).thenReturn(transformationService1Mock);
|
||||
Mockito.when(bundleContextMock.getService(serviceRef2Mock)).thenReturn(transformationService2Mock);
|
||||
|
||||
transformationHelper = new TransformationHelper(bundleContextMock);
|
||||
transformationHelper.setTransformationService(serviceRef1Mock);
|
||||
transformationHelper.setTransformationService(serviceRef2Mock);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
transformationHelper.deactivate();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissingTransformation() {
|
||||
String pattern = "TRANSFORM:pattern";
|
||||
|
||||
ChannelTransformation transformation = new ChannelTransformation(pattern);
|
||||
String result = transformation.apply(T1_INPUT).orElse(null);
|
||||
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleTransformation() {
|
||||
String pattern = T1_NAME + ":" + T1_PATTERN;
|
||||
|
||||
ChannelTransformation transformation = new ChannelTransformation(pattern);
|
||||
String result = transformation.apply(T1_INPUT).orElse(null);
|
||||
|
||||
assertEquals(T1_RESULT, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidFirstTransformation() {
|
||||
String pattern = T1_NAME + "X:" + T1_PATTERN + "∩" + T2_NAME + ":" + T2_PATTERN;
|
||||
|
||||
ChannelTransformation transformation = new ChannelTransformation(pattern);
|
||||
String result = transformation.apply(T1_INPUT).orElse(null);
|
||||
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidSecondTransformation() {
|
||||
String pattern = T1_NAME + ":" + T1_PATTERN + "∩" + T2_NAME + "X:" + T2_PATTERN;
|
||||
|
||||
ChannelTransformation transformation = new ChannelTransformation(pattern);
|
||||
String result = transformation.apply(T1_INPUT).orElse(null);
|
||||
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoubleTransformationWithoutSpaces() {
|
||||
String pattern = T1_NAME + ":" + T1_PATTERN + "∩" + T2_NAME + ":" + T2_PATTERN;
|
||||
|
||||
ChannelTransformation transformation = new ChannelTransformation(pattern);
|
||||
String result = transformation.apply(T1_INPUT).orElse(null);
|
||||
|
||||
assertEquals(T2_RESULT, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoubleTransformationWithSpaces() {
|
||||
String pattern = " " + T1_NAME + " : " + T1_PATTERN + " ∩ " + T2_NAME + " : " + T2_PATTERN + " ";
|
||||
|
||||
ChannelTransformation transformation = new ChannelTransformation(pattern);
|
||||
String result = transformation.apply(T1_INPUT).orElse(null);
|
||||
|
||||
assertEquals(T2_RESULT, result);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
/**
|
||||
* 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.converter;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.PlayPauseType;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.binding.generic.ChannelHandlerContent;
|
||||
import org.openhab.core.thing.binding.generic.ChannelTransformation;
|
||||
import org.openhab.core.thing.binding.generic.ChannelValueConverterConfig;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link ConverterTest} is a test class for state converters
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@NonNullByDefault
|
||||
public class ConverterTest {
|
||||
|
||||
private @Mock @NonNullByDefault({}) Consumer<String> sendValueMock;
|
||||
|
||||
private @Mock @NonNullByDefault({}) Consumer<State> updateStateMock;
|
||||
|
||||
private @Mock @NonNullByDefault({}) Consumer<Command> postCommandMock;
|
||||
|
||||
@Test
|
||||
public void numberItemConverter() {
|
||||
NumberChannelHandler converter = new NumberChannelHandler(updateStateMock, postCommandMock, sendValueMock,
|
||||
new ChannelTransformation(null), new ChannelTransformation(null), new ChannelValueConverterConfig());
|
||||
|
||||
// without unit
|
||||
Assertions.assertEquals(Optional.of(new DecimalType(1234)), converter.toState("1234"));
|
||||
|
||||
// unit in transformation result
|
||||
Assertions.assertEquals(Optional.of(new QuantityType<>(100, SIUnits.CELSIUS)), converter.toState("100°C"));
|
||||
|
||||
// no valid value
|
||||
Assertions.assertEquals(Optional.of(UnDefType.UNDEF), converter.toState("W"));
|
||||
Assertions.assertEquals(Optional.of(UnDefType.UNDEF), converter.toState(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void numberItemConverterWithUnit() {
|
||||
ChannelValueConverterConfig channelConfig = new ChannelValueConverterConfig();
|
||||
channelConfig.unit = "W";
|
||||
NumberChannelHandler converter = new NumberChannelHandler(updateStateMock, postCommandMock, sendValueMock,
|
||||
new ChannelTransformation(null), new ChannelTransformation(null), channelConfig);
|
||||
|
||||
// without unit
|
||||
Assertions.assertEquals(Optional.of(new QuantityType<>(500, Units.WATT)), converter.toState("500"));
|
||||
|
||||
// no valid value
|
||||
Assertions.assertEquals(Optional.of(UnDefType.UNDEF), converter.toState("foo"));
|
||||
Assertions.assertEquals(Optional.of(UnDefType.UNDEF), converter.toState(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stringTypeConverter() {
|
||||
GenericChannelHandler converter = createConverter(StringType::new);
|
||||
Assertions.assertEquals(Optional.of(new StringType("Test")), converter.toState("Test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decimalTypeConverter() {
|
||||
GenericChannelHandler converter = createConverter(DecimalType::new);
|
||||
Assertions.assertEquals(Optional.of(new DecimalType(15.6)), converter.toState("15.6"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pointTypeConverter() {
|
||||
GenericChannelHandler converter = createConverter(PointType::new);
|
||||
Assertions.assertEquals(
|
||||
Optional.of(new PointType(new DecimalType(51.1), new DecimalType(7.2), new DecimalType(100))),
|
||||
converter.toState("51.1, 7.2, 100"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void playerItemTypeConverter() {
|
||||
ChannelValueConverterConfig cfg = new ChannelValueConverterConfig();
|
||||
cfg.playValue = "PLAY";
|
||||
ChannelHandlerContent content = new ChannelHandlerContent("PLAY".getBytes(StandardCharsets.UTF_8), "UTF-8",
|
||||
null);
|
||||
PlayerChannelHandler converter = new PlayerChannelHandler(updateStateMock, postCommandMock, sendValueMock,
|
||||
new ChannelTransformation(null), new ChannelTransformation(null), cfg);
|
||||
converter.process(content);
|
||||
converter.process(content);
|
||||
|
||||
Mockito.verify(postCommandMock).accept(PlayPauseType.PLAY);
|
||||
Mockito.verify(updateStateMock, Mockito.never()).accept(ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void colorItemTypeRGBConverter() {
|
||||
ChannelValueConverterConfig cfg = new ChannelValueConverterConfig();
|
||||
cfg.colorMode = ColorChannelHandler.ColorMode.RGB;
|
||||
ChannelHandlerContent content = new ChannelHandlerContent("123,34,47".getBytes(StandardCharsets.UTF_8), "UTF-8",
|
||||
null);
|
||||
ColorChannelHandler converter = new ColorChannelHandler(updateStateMock, postCommandMock, sendValueMock,
|
||||
new ChannelTransformation(null), new ChannelTransformation(null), cfg);
|
||||
|
||||
converter.process(content);
|
||||
Mockito.verify(updateStateMock).accept(HSBType.fromRGB(123, 34, 47));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void colorItemTypeHSBConverter() {
|
||||
ChannelValueConverterConfig cfg = new ChannelValueConverterConfig();
|
||||
cfg.colorMode = ColorChannelHandler.ColorMode.HSB;
|
||||
ChannelHandlerContent content = new ChannelHandlerContent("123,34,47".getBytes(StandardCharsets.UTF_8), "UTF-8",
|
||||
null);
|
||||
ColorChannelHandler converter = new ColorChannelHandler(updateStateMock, postCommandMock, sendValueMock,
|
||||
new ChannelTransformation(null), new ChannelTransformation(null), cfg);
|
||||
|
||||
converter.process(content);
|
||||
Mockito.verify(updateStateMock).accept(new HSBType("123,34,47"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rollerSHutterConverter() {
|
||||
ChannelValueConverterConfig cfg = new ChannelValueConverterConfig();
|
||||
RollershutterChannelHandler converter = new RollershutterChannelHandler(updateStateMock, postCommandMock,
|
||||
sendValueMock, new ChannelTransformation(null), new ChannelTransformation(null), cfg);
|
||||
|
||||
// test 0 and 100
|
||||
ChannelHandlerContent content = new ChannelHandlerContent("0".getBytes(StandardCharsets.UTF_8), "UTF-8", null);
|
||||
converter.process(content);
|
||||
Mockito.verify(updateStateMock).accept(PercentType.ZERO);
|
||||
content = new ChannelHandlerContent("100".getBytes(StandardCharsets.UTF_8), "UTF-8", null);
|
||||
converter.process(content);
|
||||
Mockito.verify(updateStateMock).accept(PercentType.HUNDRED);
|
||||
|
||||
// test under/over-range (expect two times total for zero/100
|
||||
content = new ChannelHandlerContent("-1".getBytes(StandardCharsets.UTF_8), "UTF-8", null);
|
||||
converter.process(content);
|
||||
Mockito.verify(updateStateMock, Mockito.times(2)).accept(PercentType.ZERO);
|
||||
content = new ChannelHandlerContent("105".getBytes(StandardCharsets.UTF_8), "UTF-8", null);
|
||||
converter.process(content);
|
||||
Mockito.verify(updateStateMock, Mockito.times(2)).accept(PercentType.HUNDRED);
|
||||
|
||||
// test value
|
||||
content = new ChannelHandlerContent("67".getBytes(StandardCharsets.UTF_8), "UTF-8", null);
|
||||
converter.process(content);
|
||||
Mockito.verify(updateStateMock).accept(new PercentType(67));
|
||||
}
|
||||
|
||||
public GenericChannelHandler createConverter(Function<String, State> fcn) {
|
||||
return new GenericChannelHandler(fcn, updateStateMock, postCommandMock, sendValueMock,
|
||||
new ChannelTransformation(null), new ChannelTransformation(null), new ChannelValueConverterConfig());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
/**
|
||||
* 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.internal.binding.generic.converter;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.only;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.Spy;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.binding.generic.ChannelHandlerContent;
|
||||
import org.openhab.core.thing.binding.generic.ChannelTransformation;
|
||||
import org.openhab.core.thing.binding.generic.ChannelValueConverterConfig;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link AbstractTransformingItemConverterTest} is a test class for the
|
||||
* {@link AbstractTransformingChannelHandler}
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AbstractTransformingItemConverterTest {
|
||||
|
||||
@Mock
|
||||
private @NonNullByDefault({}) Consumer<String> sendHttpValue;
|
||||
|
||||
@Mock
|
||||
private @NonNullByDefault({}) Consumer<State> updateState;
|
||||
|
||||
@Mock
|
||||
private @NonNullByDefault({}) Consumer<Command> postCommand;
|
||||
|
||||
private @NonNullByDefault({}) AutoCloseable closeable;
|
||||
|
||||
@Spy
|
||||
private ChannelTransformation stateChannelTransformation = new ChannelTransformation(null);
|
||||
|
||||
@Spy
|
||||
private ChannelTransformation commandChannelTransformation = new ChannelTransformation(null);
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
closeable = MockitoAnnotations.openMocks(this);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void close() throws Exception {
|
||||
closeable.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void undefOnNullContentTest() {
|
||||
TestChannelHandler realConverter = new TestChannelHandler(updateState, postCommand, sendHttpValue,
|
||||
stateChannelTransformation, commandChannelTransformation, false);
|
||||
TestChannelHandler converter = spy(realConverter);
|
||||
|
||||
converter.process(null);
|
||||
// make sure UNDEF is send as state update
|
||||
verify(updateState, only()).accept(UnDefType.UNDEF);
|
||||
verify(postCommand, never()).accept(any());
|
||||
verify(sendHttpValue, never()).accept(any());
|
||||
|
||||
// make sure no other processing applies
|
||||
verify(converter, never()).toState(any());
|
||||
verify(converter, never()).toCommand(any());
|
||||
verify(converter, never()).toString(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commandIsPostedAsCommand() {
|
||||
TestChannelHandler converter = new TestChannelHandler(updateState, postCommand, sendHttpValue,
|
||||
stateChannelTransformation, commandChannelTransformation, true);
|
||||
|
||||
converter.process(new ChannelHandlerContent("TEST".getBytes(StandardCharsets.UTF_8), "", null));
|
||||
|
||||
// check state transformation is applied
|
||||
verify(stateChannelTransformation).apply(any());
|
||||
verify(commandChannelTransformation, never()).apply(any());
|
||||
|
||||
// check only postCommand is applied
|
||||
verify(updateState, never()).accept(any());
|
||||
verify(postCommand, only()).accept(new StringType("TEST"));
|
||||
verify(sendHttpValue, never()).accept(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateIsPostedAsUpdate() {
|
||||
TestChannelHandler converter = new TestChannelHandler(updateState, postCommand, sendHttpValue,
|
||||
stateChannelTransformation, commandChannelTransformation, false);
|
||||
|
||||
converter.process(new ChannelHandlerContent("TEST".getBytes(StandardCharsets.UTF_8), "", null));
|
||||
|
||||
// check state transformation is applied
|
||||
verify(stateChannelTransformation).apply(any());
|
||||
verify(commandChannelTransformation, never()).apply(any());
|
||||
|
||||
// check only updateState is called
|
||||
verify(updateState, only()).accept(new StringType("TEST"));
|
||||
verify(postCommand, never()).accept(any());
|
||||
verify(sendHttpValue, never()).accept(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendCommandSendsCommand() {
|
||||
TestChannelHandler converter = new TestChannelHandler(updateState, postCommand, sendHttpValue,
|
||||
stateChannelTransformation, commandChannelTransformation, false);
|
||||
|
||||
converter.send(new StringType("TEST"));
|
||||
|
||||
// check command transformation is applied
|
||||
verify(stateChannelTransformation, never()).apply(any());
|
||||
verify(commandChannelTransformation).apply(any());
|
||||
|
||||
// check only sendHttpValue is applied
|
||||
verify(updateState, never()).accept(any());
|
||||
verify(postCommand, never()).accept(any());
|
||||
verify(sendHttpValue, only()).accept("TEST");
|
||||
}
|
||||
|
||||
private static class TestChannelHandler extends AbstractTransformingChannelHandler {
|
||||
private boolean hasCommand;
|
||||
|
||||
public TestChannelHandler(Consumer<State> updateState, Consumer<Command> postCommand,
|
||||
@Nullable Consumer<String> sendValue, ChannelTransformation stateChannelTransformation,
|
||||
ChannelTransformation commandChannelTransformation, boolean hasCommand) {
|
||||
super(updateState, postCommand, sendValue, stateChannelTransformation, commandChannelTransformation,
|
||||
new ChannelValueConverterConfig());
|
||||
this.hasCommand = hasCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Command toCommand(String value) {
|
||||
return hasCommand ? new StringType(value) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<State> toState(String value) {
|
||||
return Optional.of(new StringType(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String toString(Command command) {
|
||||
return command.toString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,25 +12,34 @@
|
|||
*/
|
||||
package org.openhab.core.transform;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.IllegalFormatException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.osgi.framework.InvalidSyntaxException;
|
||||
import org.osgi.framework.ServiceReference;
|
||||
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.osgi.service.component.annotations.ReferenceCardinality;
|
||||
import org.osgi.service.component.annotations.ReferencePolicy;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
* @author Jan N. Klug - Refactored to OSGi service
|
||||
*/
|
||||
@Component(immediate = true)
|
||||
@NonNullByDefault
|
||||
public class TransformationHelper {
|
||||
private static final Map<String, TransformationService> SERVICES = new ConcurrentHashMap<>();
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TransformationHelper.class);
|
||||
|
||||
|
@ -40,6 +49,35 @@ public class TransformationHelper {
|
|||
protected static final Pattern EXTRACT_TRANSFORMFUNCTION_PATTERN = Pattern
|
||||
.compile("(.*?)\\((.*)\\)" + FUNCTION_VALUE_DELIMITER + "(.*)");
|
||||
|
||||
private final BundleContext bundleContext;
|
||||
|
||||
@Activate
|
||||
public TransformationHelper(BundleContext bundleContext) {
|
||||
this.bundleContext = bundleContext;
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
public void deactivate() {
|
||||
SERVICES.clear();
|
||||
}
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
|
||||
public void setTransformationService(ServiceReference<TransformationService> ref) {
|
||||
String key = (String) ref.getProperty(TransformationService.SERVICE_PROPERTY_NAME);
|
||||
TransformationService service = bundleContext.getService(ref);
|
||||
if (service != null) {
|
||||
SERVICES.put(key, service);
|
||||
LOGGER.debug("Added transformation service {}", key);
|
||||
}
|
||||
}
|
||||
|
||||
public void unsetTransformationService(ServiceReference<TransformationService> ref) {
|
||||
String key = (String) ref.getProperty(TransformationService.SERVICE_PROPERTY_NAME);
|
||||
if (SERVICES.remove(key) != null) {
|
||||
LOGGER.debug("Removed transformation service {}", key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* determines whether a pattern refers to a transformation service
|
||||
*
|
||||
|
@ -50,52 +88,57 @@ public class TransformationHelper {
|
|||
return EXTRACT_TRANSFORMFUNCTION_PATTERN.matcher(pattern).matches();
|
||||
}
|
||||
|
||||
public static @Nullable TransformationService getTransformationService(String serviceName) {
|
||||
return SERVICES.get(serviceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the OSGi service registry for a service that provides a transformation service of
|
||||
* a given transformation type (e.g. REGEX, XSLT, etc.)
|
||||
* Return the transformation service that provides a given transformation type (e.g. REGEX, XSLT, etc.)
|
||||
*
|
||||
* @param context the bundle context which can be null
|
||||
* @param transformationType the desired transformation type
|
||||
* @return a service instance or null, if none could be found
|
||||
*
|
||||
* @deprecated use {@link #getTransformationService(String)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public static @Nullable TransformationService getTransformationService(@Nullable BundleContext context,
|
||||
String transformationType) {
|
||||
if (context != null) {
|
||||
String filter = "(" + TransformationService.SERVICE_PROPERTY_NAME + "=" + transformationType + ")";
|
||||
try {
|
||||
Collection<ServiceReference<TransformationService>> refs = context
|
||||
.getServiceReferences(TransformationService.class, filter);
|
||||
if (refs != null && !refs.isEmpty()) {
|
||||
return context.getService(refs.iterator().next());
|
||||
} else {
|
||||
LOGGER.debug("Cannot get service reference for transformation service of type {}",
|
||||
transformationType);
|
||||
}
|
||||
} catch (InvalidSyntaxException e) {
|
||||
LOGGER.debug("Cannot get service reference for transformation service of type {}", transformationType,
|
||||
e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return getTransformationService(transformationType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a state string using transformation functions within a given pattern.
|
||||
*
|
||||
* @param context a valid bundle context, required for accessing the services
|
||||
* @param stateDescPattern the pattern that contains the transformation instructions
|
||||
* @param transformationString the pattern that contains the transformation instructions
|
||||
* @param state the state to be formatted before being passed into the transformation function
|
||||
* @return the result of the transformation. If no transformation was done, <code>null</code> is returned
|
||||
* @throws TransformationException if transformation service is not available or the transformation failed
|
||||
*
|
||||
* @deprecated Use {@link #transform(String, String)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public static @Nullable String transform(BundleContext context, String transformationString, String state)
|
||||
throws TransformationException {
|
||||
return transform(transformationString, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a state string using transformation functions within a given pattern.
|
||||
*
|
||||
* @param transformationString the pattern that contains the transformation instructions
|
||||
* @param state the state to be formatted before being passed into the transformation function
|
||||
* @return the result of the transformation. If no transformation was done, <code>null</code> is returned
|
||||
* @throws TransformationException if transformation service is not available or the transformation failed
|
||||
*/
|
||||
public static @Nullable String transform(BundleContext context, String stateDescPattern, String state)
|
||||
throws TransformationException {
|
||||
Matcher matcher = EXTRACT_TRANSFORMFUNCTION_PATTERN.matcher(stateDescPattern);
|
||||
public static @Nullable String transform(String transformationString, String state) throws TransformationException {
|
||||
Matcher matcher = EXTRACT_TRANSFORMFUNCTION_PATTERN.matcher(transformationString);
|
||||
if (matcher.find()) {
|
||||
String type = matcher.group(1);
|
||||
String pattern = matcher.group(2);
|
||||
String value = matcher.group(3);
|
||||
TransformationService transformation = TransformationHelper.getTransformationService(context, type);
|
||||
TransformationService transformation = SERVICES.get(type);
|
||||
if (transformation != null) {
|
||||
return transform(transformation, pattern, value, state);
|
||||
} else {
|
||||
|
|
|
@ -66,4 +66,5 @@ Fragment-Host: org.openhab.core.automation
|
|||
net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\
|
||||
org.mockito.mockito-core;version='[4.11.0,4.11.1)',\
|
||||
org.objenesis;version='[3.3.0,3.3.1)',\
|
||||
org.osgi.service.cm;version='[1.6.0,1.6.1)'
|
||||
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
|
||||
org.openhab.core.transform;version='[4.0.0,4.0.1)'
|
||||
|
|
|
@ -66,4 +66,5 @@ Fragment-Host: org.openhab.core.automation
|
|||
net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\
|
||||
org.mockito.mockito-core;version='[4.11.0,4.11.1)',\
|
||||
org.objenesis;version='[3.3.0,3.3.1)',\
|
||||
org.osgi.service.cm;version='[1.6.0,1.6.1)'
|
||||
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
|
||||
org.openhab.core.transform;version='[4.0.0,4.0.1)'
|
||||
|
|
|
@ -66,4 +66,5 @@ Fragment-Host: org.openhab.core.automation
|
|||
net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\
|
||||
org.mockito.mockito-core;version='[4.11.0,4.11.1)',\
|
||||
org.objenesis;version='[3.3.0,3.3.1)',\
|
||||
org.osgi.service.cm;version='[1.6.0,1.6.1)'
|
||||
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
|
||||
org.openhab.core.transform;version='[4.0.0,4.0.1)'
|
||||
|
|
|
@ -66,4 +66,5 @@ Fragment-Host: org.openhab.core.automation
|
|||
net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\
|
||||
org.mockito.mockito-core;version='[4.11.0,4.11.1)',\
|
||||
org.objenesis;version='[3.3.0,3.3.1)',\
|
||||
org.osgi.service.cm;version='[1.6.0,1.6.1)'
|
||||
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
|
||||
org.openhab.core.transform;version='[4.0.0,4.0.1)'
|
||||
|
|
|
@ -65,4 +65,6 @@ Fragment-Host: org.openhab.core.config.discovery.mdns
|
|||
net.bytebuddy.byte-buddy;version='[1.12.19,1.12.20)',\
|
||||
net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\
|
||||
org.mockito.mockito-core;version='[4.11.0,4.11.1)',\
|
||||
org.objenesis;version='[3.3.0,3.3.1)'
|
||||
org.objenesis;version='[3.3.0,3.3.1)',\
|
||||
org.openhab.core.transform;version='[4.0.0,4.0.1)',\
|
||||
org.osgi.service.cm;version='[1.6.0,1.6.1)'
|
||||
|
|
|
@ -64,4 +64,6 @@ Fragment-Host: org.openhab.core.config.discovery
|
|||
net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\
|
||||
org.mockito.junit-jupiter;version='[4.11.0,4.11.1)',\
|
||||
org.mockito.mockito-core;version='[4.11.0,4.11.1)',\
|
||||
org.objenesis;version='[3.3.0,3.3.1)'
|
||||
org.objenesis;version='[3.3.0,3.3.1)',\
|
||||
org.openhab.core.transform;version='[4.0.0,4.0.1)',\
|
||||
org.osgi.service.cm;version='[1.6.0,1.6.1)'
|
||||
|
|
|
@ -65,4 +65,6 @@ Fragment-Host: org.openhab.core.config.discovery.usbserial.linuxsysfs
|
|||
net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\
|
||||
org.mockito.junit-jupiter;version='[4.11.0,4.11.1)',\
|
||||
org.mockito.mockito-core;version='[4.11.0,4.11.1)',\
|
||||
org.objenesis;version='[3.3.0,3.3.1)'
|
||||
org.objenesis;version='[3.3.0,3.3.1)',\
|
||||
org.openhab.core.transform;version='[4.0.0,4.0.1)',\
|
||||
org.osgi.service.cm;version='[1.6.0,1.6.1)'
|
||||
|
|
|
@ -73,4 +73,6 @@ Provide-Capability: \
|
|||
net.bytebuddy.byte-buddy;version='[1.12.19,1.12.20)',\
|
||||
net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\
|
||||
org.mockito.mockito-core;version='[4.11.0,4.11.1)',\
|
||||
org.objenesis;version='[3.3.0,3.3.1)'
|
||||
org.objenesis;version='[3.3.0,3.3.1)',\
|
||||
org.openhab.core.transform;version='[4.0.0,4.0.1)',\
|
||||
org.osgi.service.cm;version='[1.6.0,1.6.1)'
|
||||
|
|
|
@ -58,4 +58,6 @@ Fragment-Host: org.openhab.core.storage.json
|
|||
junit-jupiter-engine;version='[5.9.2,5.9.3)',\
|
||||
junit-platform-commons;version='[1.9.2,1.9.3)',\
|
||||
junit-platform-engine;version='[1.9.2,1.9.3)',\
|
||||
junit-platform-launcher;version='[1.9.2,1.9.3)'
|
||||
junit-platform-launcher;version='[1.9.2,1.9.3)',\
|
||||
org.openhab.core.transform;version='[4.0.0,4.0.1)',\
|
||||
org.osgi.service.cm;version='[1.6.0,1.6.1)'
|
||||
|
|
|
@ -66,4 +66,5 @@ Fragment-Host: org.openhab.core.thing
|
|||
net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\
|
||||
org.mockito.junit-jupiter;version='[4.11.0,4.11.1)',\
|
||||
org.mockito.mockito-core;version='[4.11.0,4.11.1)',\
|
||||
org.objenesis;version='[3.3.0,3.3.1)'
|
||||
org.objenesis;version='[3.3.0,3.3.1)',\
|
||||
org.openhab.core.transform;version='[4.0.0,4.0.1)'
|
||||
|
|
|
@ -72,4 +72,5 @@ Fragment-Host: org.openhab.core.voice
|
|||
junit-jupiter-params;version='[5.9.2,5.9.3)',\
|
||||
junit-platform-commons;version='[1.9.2,1.9.3)',\
|
||||
junit-platform-engine;version='[1.9.2,1.9.3)',\
|
||||
junit-platform-launcher;version='[1.9.2,1.9.3)'
|
||||
junit-platform-launcher;version='[1.9.2,1.9.3)',\
|
||||
org.openhab.core.transform;version='[4.0.0,4.0.1)'
|
||||
|
|
Loading…
Reference in New Issue