2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.modbus.internal;
15 import java.util.List;
16 import java.util.Objects;
17 import java.util.Optional;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.openhab.core.library.types.DecimalType;
22 import org.openhab.core.library.types.OnOffType;
23 import org.openhab.core.library.types.OpenClosedType;
24 import org.openhab.core.thing.binding.generic.ChannelTransformation;
25 import org.openhab.core.types.Command;
26 import org.openhab.core.types.State;
27 import org.openhab.core.types.TypeParser;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
33 * Class for performing transformations of a command or state.
35 * @author Jimmy Tanagra - Initial contribution
39 public class ModbusTransformation {
41 public static final String TRANSFORM_DEFAULT = "default";
44 * Ordered list of types that are tried out first when trying to parse transformed command
46 private static final List<Class<? extends Command>> DEFAULT_TYPES = List.of( //
48 OpenClosedType.class, //
52 private final Logger logger = LoggerFactory.getLogger(ModbusTransformation.class);
53 private final @Nullable ChannelTransformation transformation;
54 private final @Nullable String constantOutput;
57 * Creates a transformation object.
59 * The transformations are chained and applied in the order they are given in the list.
60 * Each transformation can also contain the intersection symbol "∩" to separate
61 * multiple transformations in one line.
63 * - If the transformationList is null or consists of only blank strings,
64 * the output of the transformation will be an empty string regardless of the input.
66 * - If first element is "default", the transformation will be considered as
67 * an identity transformation, which returns the input as the output.
68 * Additional elements in the list are ignored.
70 * - If the transformationList contains valid transformation syntax, the output
71 * will be transformed according to the given transformations.
73 * - If the first element is some other value, it is treated as a constant and it
74 * will become the output of the transformation, regardless of the input.
75 * Additional elements in the list are ignored.
77 * @param transformations a list of transformations to apply.
79 public ModbusTransformation(@Nullable List<String> transformationList) {
80 if (transformationList == null || transformationList.isEmpty()
81 || transformationList.stream().allMatch(String::isBlank)) {
82 transformation = null;
87 int size = transformationList.size();
88 String firstLine = transformationList.get(0).trim();
90 if (size == 1 && firstLine.equalsIgnoreCase(TRANSFORM_DEFAULT)) {
91 // no-op (identity) transformation
92 transformation = null;
93 constantOutput = null;
97 if (transformationList.stream().allMatch(ChannelTransformation::isValidTransformation)) {
98 transformation = new ChannelTransformation(transformationList);
99 constantOutput = null;
101 transformation = null;
102 constantOutput = firstLine;
105 "Given transformation configuration {} did not match the correct pattern. Transformation output will be constant '{}'",
106 transformationList, constantOutput);
108 logger.debug("The output for transformation {} will be constant '{}'", transformationList,
114 public String transform(String value) {
115 if (transformation != null) {
116 // return input if transformation failed
117 return Objects.requireNonNull(transformation.apply(value).orElse(value));
120 return Objects.requireNonNullElse(constantOutput, value);
123 public boolean isIdentityTransform() {
124 return transformation == null && constantOutput == null;
127 public static Optional<Command> tryConvertToCommand(String transformed) {
128 return Optional.ofNullable(TypeParser.parseCommand(DEFAULT_TYPES, transformed));
132 * Transform state to another state using this transformation
134 * @param types types to used to parse the transformation result
136 * @return Transformed command, or null if no transformation was possible
138 public @Nullable State transformState(List<Class<? extends State>> types, State state) {
139 // Note that even identity transformations go through the State -> String -> State steps. This does add some
140 // overhead but takes care of DecimalType -> PercentType conversions, for example.
141 final String stateAsString = state.toString();
142 final String transformed = transform(stateAsString);
143 return TypeParser.parseState(types, transformed);