added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.transform.scale/.classpath
Normal file
32
bundles/org.openhab.transform.scale/.classpath
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
23
bundles/org.openhab.transform.scale/.project
Normal file
23
bundles/org.openhab.transform.scale/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.transform.scale</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
13
bundles/org.openhab.transform.scale/NOTICE
Normal file
13
bundles/org.openhab.transform.scale/NOTICE
Normal file
@@ -0,0 +1,13 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
88
bundles/org.openhab.transform.scale/README.md
Normal file
88
bundles/org.openhab.transform.scale/README.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# Scale Transformation Service
|
||||
|
||||
The Scale Transformation Service is an easy to handle tool that can help you with the discretization of number inputs.
|
||||
It transforms a given input by matching it to specified ranges.
|
||||
The input string must be in numerical format.
|
||||
|
||||
The file is expected to exist in the `transform` configuration directory and its ending has to be `.scale`.
|
||||
It should follow the format given in the table below.
|
||||
|
||||
Range expressions always contain two parts.
|
||||
The range to scale on, which is located left from the equality sign and the corresponding output string on the right of it.
|
||||
A range consists of two bounds. Both are optional, the range is then open. Both bounds can be inclusive or exclusive.
|
||||
|
||||
| Scale Expression | Returns XYZ when the given value is |
|
||||
|------------------|------------------------------------------------------------|
|
||||
| `[12..23]=XYZ` | `between (or equal to) 12 and 23` |
|
||||
| `]12..23[=XYZ` | `between 12 and 23 (12 and 23 are excluded in this case.)` |
|
||||
| `[..23]=XYZ` | `lower than or equal to 23` |
|
||||
| `]12..]=XYZ` | `greater than 12` |
|
||||
|
||||
These expressions are evaluated from top to bottom.
|
||||
The first range that includes the value is selected.
|
||||
|
||||
## Special entries
|
||||
Some special entries can be used in the scale file.
|
||||
|
||||
### Catchall Entry
|
||||
`[..]=Catchall`
|
||||
|
||||
This entry will match all numeric values not met by a previous range. Obviously, this one should be put at the very end of the scale definition file.
|
||||
|
||||
### Not A Number
|
||||
Scale transform is designed to work with numeric or quantity states. When the value presented to scale transform does not match this (most of the time with NULL or UNDEF states) it will not be handled and a warning is raised in the openhab.log . This case can be smoothly avoided with a
|
||||
|
||||
`NaN=Non Numeric State presented`
|
||||
|
||||
### Formatting output
|
||||
At last, Scale transform can take care of formatting an output with this entry :
|
||||
|
||||
`format=%label% (%value%) !`
|
||||
|
||||
Where :
|
||||
- `%label%` will be replaced by transformed value and
|
||||
- `%value%` is the numeric value presen
|
||||
## Example
|
||||
|
||||
The following example shows how to break down numeric UV values into fixed UV index categories.
|
||||
We have an example UV sensor that sends numeric values from `0` to `100`, which we then want to scale into the [UV Index](https://en.wikipedia.org/wiki/Ultraviolet_index) range.
|
||||
|
||||
Example item:
|
||||
|
||||
```java
|
||||
Number Uv_Sensor_Level "UV Level [SCALE(uvindex.scale):%s]"
|
||||
```
|
||||
|
||||
Referenced scale file `uvindex.scale` in the `transform` folder:
|
||||
|
||||
```python
|
||||
[..3]=1
|
||||
]3..6]=2
|
||||
]6..8]=3
|
||||
]8..10]=4
|
||||
]10..100]=5
|
||||
```
|
||||
|
||||
Each value the item receives, will be categorized to one of the five given ranges.
|
||||
Values **lower than or equal to 3** are matched with `[..3]=1`.
|
||||
Greater values are catched in ranges with 2 values as criteria.
|
||||
The only condition here is that the received value has to be lower or equal than `100` in our example, since we haven't defined other cases yet.
|
||||
If **none** of the configured conditions matches the given value, the response will be empty.
|
||||
|
||||
Please note that all ranges for values above **3** are opened with a `]`.
|
||||
So the border values (3, 6, 8 and 10) are always transformed to the lower range, since the `]` excludes the given critera.
|
||||
|
||||
## Usage as a Profile
|
||||
|
||||
The functionality of this `TransformationService` can be used in a `Profile` on an `ItemChannelLink` too.
|
||||
To do so, it can be configured in the `.items` file as follows:
|
||||
|
||||
```java
|
||||
String <itemName> { channel="<channelUID>"[profile="transform:SCALE", function="<filename>", sourceFormat="<valueFormat>"]}
|
||||
```
|
||||
|
||||
The filename (within the `transform` folder) of the scale file has to be set in the `function` parameter.
|
||||
The parameter `sourceFormat` is optional and can be used to format the input value **before** the transformation, i.e. `%.3f`.
|
||||
If omitted the default is `%s`, so the input value will be put into the transformation without any format changes.
|
||||
|
||||
Please note: This profile is a one-way transformation, i.e. only values from a device towards the item are changed, the other direction is left untouched.
|
||||
@@ -0,0 +1,5 @@
|
||||
[-8..10[=low
|
||||
[10..20[=middle
|
||||
[20..300[=high
|
||||
[300..]=extreme
|
||||
[..]=catchall
|
||||
@@ -0,0 +1,5 @@
|
||||
[-8..10[=low
|
||||
[10..20[=middle
|
||||
[20..300[=high
|
||||
[300..]=extreme
|
||||
NaN=Non Numeric
|
||||
@@ -0,0 +1,4 @@
|
||||
]..10[=low
|
||||
[1O..20[=middle
|
||||
[20..300[=high
|
||||
[300..]=extreme
|
||||
@@ -0,0 +1,3 @@
|
||||
]..15[=first
|
||||
[10..17[=second
|
||||
[15..[=last
|
||||
@@ -0,0 +1,7 @@
|
||||
[-40..20]=no significant
|
||||
[20..29]=comfortable
|
||||
[29..38]=some discomfort
|
||||
[38..45]=avoid exertion
|
||||
[45..54]=dangerous
|
||||
[54..100]=heat stroke imminent
|
||||
NaN=unknown
|
||||
@@ -0,0 +1,6 @@
|
||||
[-40..20]=nicht wesentlich
|
||||
[20..29]=komfortabel
|
||||
[29..38]=etwas unannehmlich
|
||||
[38..45]=Anstrengung vermeiden
|
||||
[45..54]=gefährlich
|
||||
[54..100]=Hitzschlag bevorstehend
|
||||
@@ -0,0 +1,6 @@
|
||||
[-40..20]=non significatif
|
||||
[20..29]=confortable
|
||||
[29..38]=quelque inconfort
|
||||
[38..45]=éviter de s'exposer
|
||||
[45..54]=dangereux
|
||||
[54..100]=coup de soleil imminent
|
||||
@@ -0,0 +1,4 @@
|
||||
]..10[=low
|
||||
[10..20[=middle
|
||||
[20..300[=high
|
||||
[300..]=extreme
|
||||
@@ -0,0 +1,6 @@
|
||||
[0..899]=Excellente qualite d'air
|
||||
[900..1099]=Correcte
|
||||
[1100..1599]=Air intérieur contamine, aeration recommandee
|
||||
[1600..10000]=Air intérieur fortement contamine
|
||||
[..]=inconnu ⁉
|
||||
format=%label% (%value%) !
|
||||
17
bundles/org.openhab.transform.scale/pom.xml
Normal file
17
bundles/org.openhab.transform.scale/pom.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.transform.scale</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Transformation Service :: Scale</name>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.transform.scale-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-transformation-scale" description="Scale Transformation" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="75">mvn:org.openhab.addons.bundles/org.openhab.transform.scale/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.transform.scale.internal;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* Range implementation using BigDecimals.
|
||||
*
|
||||
* @author Markus Rathgeb - Initial contribution
|
||||
*/
|
||||
public class Range {
|
||||
|
||||
public static Range open(final BigDecimal lower, final BigDecimal upper) {
|
||||
return new Range(lower, false, upper, false);
|
||||
}
|
||||
|
||||
public static Range closed(final BigDecimal lower, final BigDecimal upper) {
|
||||
return new Range(lower, true, upper, true);
|
||||
}
|
||||
|
||||
public static Range openClosed(final BigDecimal lower, final BigDecimal upper) {
|
||||
return new Range(lower, false, upper, true);
|
||||
}
|
||||
|
||||
public static Range closedOpen(final BigDecimal lower, final BigDecimal upper) {
|
||||
return new Range(lower, true, upper, false);
|
||||
}
|
||||
|
||||
public static Range greaterThan(final BigDecimal lower) {
|
||||
return new Range(lower, false, null, false);
|
||||
}
|
||||
|
||||
public static Range atLeast(final BigDecimal lower) {
|
||||
return new Range(lower, true, null, false);
|
||||
}
|
||||
|
||||
public static Range lessThan(final BigDecimal upper) {
|
||||
return new Range(null, false, upper, false);
|
||||
}
|
||||
|
||||
public static Range atMost(final BigDecimal upper) {
|
||||
return new Range(null, false, upper, true);
|
||||
}
|
||||
|
||||
public static Range all() {
|
||||
return new Range(null, false, null, false);
|
||||
}
|
||||
|
||||
public static Range range(final BigDecimal lower, final boolean lowerInclusive, final BigDecimal upper,
|
||||
final boolean upperInclusive) {
|
||||
return new Range(lower, lowerInclusive, upper, upperInclusive);
|
||||
}
|
||||
|
||||
final BigDecimal min;
|
||||
final boolean minInclusive;
|
||||
final BigDecimal max;
|
||||
final boolean maxInclusive;
|
||||
|
||||
private Range(final BigDecimal min, final boolean minInclusive, final BigDecimal max, final boolean maxInclusive) {
|
||||
this.min = min;
|
||||
this.minInclusive = minInclusive;
|
||||
this.max = max;
|
||||
this.maxInclusive = maxInclusive;
|
||||
}
|
||||
|
||||
public boolean contains(final BigDecimal value) {
|
||||
final boolean minMatch;
|
||||
if (min == null) {
|
||||
minMatch = true;
|
||||
} else {
|
||||
int cmp = value.compareTo(min);
|
||||
if (minInclusive) {
|
||||
minMatch = cmp == 0 || cmp == 1;
|
||||
} else {
|
||||
minMatch = cmp == 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!minMatch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean maxMatch;
|
||||
if (max == null) {
|
||||
maxMatch = true;
|
||||
} else {
|
||||
int cmp = value.compareTo(max);
|
||||
if (maxInclusive) {
|
||||
maxMatch = cmp == 0 || cmp == -1;
|
||||
} else {
|
||||
maxMatch = cmp == -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!maxMatch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.transform.scale.internal;
|
||||
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.transform.AbstractFileTransformationService;
|
||||
import org.openhab.core.transform.TransformationException;
|
||||
import org.openhab.core.transform.TransformationService;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The implementation of {@link TransformationService} which transforms the
|
||||
* input by matching it between limits of ranges in a scale file
|
||||
*
|
||||
* @author Gaël L'hopital
|
||||
* @author Markus Rathgeb - drop usage of Guava
|
||||
*/
|
||||
@Component(immediate = true, service = TransformationService.class, property = { "smarthome.transform=SCALE" })
|
||||
public class ScaleTransformationService extends AbstractFileTransformationService<Map<Range, String>> {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ScaleTransformationService.class);
|
||||
|
||||
/** RegEx to extract a scale definition */
|
||||
private static final Pattern LIMITS_PATTERN = Pattern.compile("(\\[|\\])(.*)\\.\\.(.*)(\\[|\\])");
|
||||
|
||||
private static final String NON_NUMBER = "NaN";
|
||||
private static final String FORMAT = "format";
|
||||
private static final String FORMAT_VALUE = "%value%";
|
||||
private static final String FORMAT_LABEL = "%label%";
|
||||
|
||||
/** Inaccessible range used to store presentation format ]0..0[ */
|
||||
private static final Range FORMAT_RANGE = Range.range(BigDecimal.ZERO, false, BigDecimal.ZERO, false);
|
||||
|
||||
/**
|
||||
* The implementation of {@link OrderedProperties} that let access
|
||||
* properties in the same order than presented in the source file
|
||||
* by using the orderedKeys function.
|
||||
*
|
||||
* This implementation is limited to the sole purpose of the class
|
||||
* (e.g. it does not handle removing elements)
|
||||
*
|
||||
* @author Gaël L'hopital
|
||||
*/
|
||||
static class OrderedProperties extends Properties {
|
||||
private static final long serialVersionUID = 3860553217028220119L;
|
||||
private final HashSet<Object> keys = new LinkedHashSet<>();
|
||||
|
||||
Set<Object> orderedKeys() {
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<Object> keys() {
|
||||
return Collections.<Object> enumeration(keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object put(Object key, Object value) {
|
||||
keys.add(key);
|
||||
return super.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs transformation of the input <code>source</code>
|
||||
*
|
||||
* The method transforms the input <code>source</code> by matching searching
|
||||
* the range where it fits i.e. [min..max]=value or ]min..max]=value
|
||||
*
|
||||
* @param properties the list of properties defining all the available ranges
|
||||
* @param source the input to transform
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
protected String internalTransform(Map<Range, String> data, String source) throws TransformationException {
|
||||
try {
|
||||
final BigDecimal value = new BigDecimal(source);
|
||||
|
||||
return formatResult(data, source, value);
|
||||
} catch (NumberFormatException e) {
|
||||
// Scale can only be used with numeric inputs, so lets try to see if ever its a valid quantity type
|
||||
try {
|
||||
final QuantityType<?> quantity = new QuantityType<>(source);
|
||||
return formatResult(data, source, quantity.toBigDecimal());
|
||||
} catch (NumberFormatException e2) {
|
||||
String nonNumeric = data.get(null);
|
||||
if (nonNumeric != null) {
|
||||
return nonNumeric;
|
||||
} else {
|
||||
throw new TransformationException(
|
||||
"Scale must be used with numeric inputs, valid quantity types or a 'NaN' entry.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String formatResult(Map<Range, String> data, String source, final BigDecimal value)
|
||||
throws TransformationException {
|
||||
String format = data.get(FORMAT_RANGE);
|
||||
String result = getScaleResult(data, source, value);
|
||||
return format.replaceAll(FORMAT_VALUE, source).replaceAll(FORMAT_LABEL, result);
|
||||
}
|
||||
|
||||
private String getScaleResult(Map<Range, String> data, String source, final BigDecimal value)
|
||||
throws TransformationException {
|
||||
return data.entrySet().stream().filter(entry -> entry.getKey() != null && entry.getKey().contains(value))
|
||||
.findFirst().map(Map.Entry::getValue)
|
||||
.orElseThrow(() -> new TransformationException("No matching range for '" + source + "'"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<Range, String> internalLoadTransform(String filename) throws TransformationException {
|
||||
try (FileReader reader = new FileReader(filename)) {
|
||||
final Map<Range, String> data = new LinkedHashMap<>();
|
||||
data.put(FORMAT_RANGE, FORMAT_LABEL);
|
||||
final OrderedProperties properties = new OrderedProperties();
|
||||
properties.load(reader);
|
||||
|
||||
for (Object orderedKey : properties.orderedKeys()) {
|
||||
final String entry = (String) orderedKey;
|
||||
final String value = properties.getProperty(entry);
|
||||
final Matcher matcher = LIMITS_PATTERN.matcher(entry);
|
||||
if (matcher.matches() && (matcher.groupCount() == 4)) {
|
||||
final boolean lowerInclusive = matcher.group(1).equals("[");
|
||||
final boolean upperInclusive = matcher.group(4).equals("]");
|
||||
|
||||
final String lowLimit = matcher.group(2);
|
||||
final String highLimit = matcher.group(3);
|
||||
|
||||
try {
|
||||
final BigDecimal lowValue = lowLimit.isEmpty() ? null : new BigDecimal(lowLimit);
|
||||
final BigDecimal highValue = highLimit.isEmpty() ? null : new BigDecimal(highLimit);
|
||||
final Range range = Range.range(lowValue, lowerInclusive, highValue, upperInclusive);
|
||||
|
||||
data.put(range, value);
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new TransformationException("Error parsing bounds: " + lowLimit + ".." + highLimit);
|
||||
}
|
||||
} else {
|
||||
if (NON_NUMBER.equals(entry)) {
|
||||
data.put(null, value);
|
||||
} else if (FORMAT.equals(entry)) {
|
||||
data.put(FORMAT_RANGE, value);
|
||||
} else {
|
||||
logger.warn("Scale transform file '{}' does not comply with syntax for entry : '{}', '{}'",
|
||||
filename, entry, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (final IOException ex) {
|
||||
throw new TransformationException("An error occurred while opening file.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.transform.scale.internal.profiles;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.profiles.ProfileCallback;
|
||||
import org.openhab.core.thing.profiles.ProfileContext;
|
||||
import org.openhab.core.thing.profiles.ProfileTypeUID;
|
||||
import org.openhab.core.thing.profiles.StateProfile;
|
||||
import org.openhab.core.transform.TransformationException;
|
||||
import org.openhab.core.transform.TransformationHelper;
|
||||
import org.openhab.core.transform.TransformationService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.Type;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Profile to offer the ScaleTransformationservice on a ItemChannelLink
|
||||
*
|
||||
* @author Stefan Triller - initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ScaleTransformationProfile implements StateProfile {
|
||||
|
||||
public static final ProfileTypeUID PROFILE_TYPE_UID = new ProfileTypeUID(
|
||||
TransformationService.TRANSFORM_PROFILE_SCOPE, "SCALE");
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ScaleTransformationProfile.class);
|
||||
|
||||
private final TransformationService service;
|
||||
private final ProfileCallback callback;
|
||||
|
||||
private static final String FUNCTION_PARAM = "function";
|
||||
private static final String SOURCE_FORMAT_PARAM = "sourceFormat";
|
||||
|
||||
@NonNullByDefault({})
|
||||
private final String function;
|
||||
@NonNullByDefault({})
|
||||
private final String sourceFormat;
|
||||
|
||||
public ScaleTransformationProfile(ProfileCallback callback, ProfileContext context, TransformationService service) {
|
||||
this.service = service;
|
||||
this.callback = callback;
|
||||
|
||||
Object paramFunction = context.getConfiguration().get(FUNCTION_PARAM);
|
||||
Object paramSource = context.getConfiguration().get(SOURCE_FORMAT_PARAM);
|
||||
|
||||
logger.debug("Profile configured with '{}'='{}', '{}'={}", FUNCTION_PARAM, paramFunction, SOURCE_FORMAT_PARAM,
|
||||
paramSource);
|
||||
// SOURCE_FORMAT_PARAM is an advanced parameter and we assume "%s" if it is not set
|
||||
if (paramSource == null) {
|
||||
paramSource = "%s";
|
||||
}
|
||||
if (paramFunction instanceof String && paramSource instanceof String) {
|
||||
function = (String) paramFunction;
|
||||
sourceFormat = (String) paramSource;
|
||||
} else {
|
||||
logger.error("Parameter '{}' and '{}' have to be Strings. Profile will be inactive.", FUNCTION_PARAM,
|
||||
SOURCE_FORMAT_PARAM);
|
||||
function = null;
|
||||
sourceFormat = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProfileTypeUID getProfileTypeUID() {
|
||||
return PROFILE_TYPE_UID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateUpdateFromItem(State state) {
|
||||
callback.handleUpdate(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommandFromItem(Command command) {
|
||||
callback.handleCommand(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommandFromHandler(Command command) {
|
||||
if (function == null || sourceFormat == null) {
|
||||
logger.warn(
|
||||
"Please specify a function and a source format for this Profile in the '{}', and '{}' parameters. Returning the original command now.",
|
||||
FUNCTION_PARAM, SOURCE_FORMAT_PARAM);
|
||||
callback.sendCommand(command);
|
||||
return;
|
||||
}
|
||||
callback.sendCommand((Command) transformState(command));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateUpdateFromHandler(State state) {
|
||||
if (function == null || sourceFormat == null) {
|
||||
logger.warn(
|
||||
"Please specify a function and a source format for this Profile in the '{}' and '{}' parameters. Returning the original state now.",
|
||||
FUNCTION_PARAM, SOURCE_FORMAT_PARAM);
|
||||
callback.sendUpdate(state);
|
||||
return;
|
||||
}
|
||||
callback.sendUpdate((State) transformState(state));
|
||||
}
|
||||
|
||||
private Type transformState(Type state) {
|
||||
String result = state.toFullString();
|
||||
try {
|
||||
result = TransformationHelper.transform(service, function, sourceFormat, state.toFullString());
|
||||
} catch (TransformationException e) {
|
||||
logger.warn("Could not transform state '{}' with function '{}' and format '{}'", state, function,
|
||||
sourceFormat);
|
||||
}
|
||||
StringType resultType = new StringType(result);
|
||||
logger.debug("Transformed '{}' into '{}'", state, resultType);
|
||||
return resultType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.transform.scale.internal.profiles;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.profiles.Profile;
|
||||
import org.openhab.core.thing.profiles.ProfileCallback;
|
||||
import org.openhab.core.thing.profiles.ProfileContext;
|
||||
import org.openhab.core.thing.profiles.ProfileFactory;
|
||||
import org.openhab.core.thing.profiles.ProfileType;
|
||||
import org.openhab.core.thing.profiles.ProfileTypeBuilder;
|
||||
import org.openhab.core.thing.profiles.ProfileTypeProvider;
|
||||
import org.openhab.core.thing.profiles.ProfileTypeUID;
|
||||
import org.openhab.core.transform.TransformationService;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* Profilefactory that creates the transformation profile for the scale transformation service
|
||||
*
|
||||
* @author Stefan Triller - initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { ProfileFactory.class, ProfileTypeProvider.class })
|
||||
public class ScaleTransformationProfileFactory implements ProfileFactory, ProfileTypeProvider {
|
||||
|
||||
@NonNullByDefault({})
|
||||
private TransformationService service;
|
||||
|
||||
@Override
|
||||
public Collection<ProfileType> getProfileTypes(@Nullable Locale locale) {
|
||||
return Arrays.asList(ProfileTypeBuilder.newState(ScaleTransformationProfile.PROFILE_TYPE_UID,
|
||||
ScaleTransformationProfile.PROFILE_TYPE_UID.getId()).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Profile createProfile(ProfileTypeUID profileTypeUID, ProfileCallback callback,
|
||||
ProfileContext profileContext) {
|
||||
return new ScaleTransformationProfile(callback, profileContext, service);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ProfileTypeUID> getSupportedProfileTypeUIDs() {
|
||||
return Arrays.asList(ScaleTransformationProfile.PROFILE_TYPE_UID);
|
||||
}
|
||||
|
||||
@Reference(target = "(smarthome.transform=SCALE)")
|
||||
public void addTransformationService(TransformationService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
public void removeTransformationService(TransformationService service) {
|
||||
this.service = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="profile:transform:SCALE">
|
||||
<parameter name="function" type="text" required="true">
|
||||
<label>Filename</label>
|
||||
<description>Filename containing the scale mappings.</description>
|
||||
</parameter>
|
||||
<parameter name="sourceFormat" type="text" required="false">
|
||||
<label>State Formatter</label>
|
||||
<description>How to format the state on the channel before transforming it, i.e. %s or %.1f °C (default is %s)</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1 @@
|
||||
Bundle resources go in here!
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.transform.scale.internal;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.measure.quantity.Dimensionless;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.transform.TransformationException;
|
||||
|
||||
/**
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
public class ScaleTransformServiceTest {
|
||||
private ScaleTransformationService processor;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
processor = new ScaleTransformationService() {
|
||||
@Override
|
||||
protected Locale getLocale() {
|
||||
return Locale.US;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransformByScale() throws TransformationException {
|
||||
// need to be sure we'll have the german version
|
||||
String existingscale = "scale/humidex_de.scale";
|
||||
String source = "10";
|
||||
String transformedResponse = processor.transform(existingscale, source);
|
||||
Assert.assertEquals("nicht wesentlich", transformedResponse);
|
||||
|
||||
existingscale = "scale/limits.scale";
|
||||
source = "10";
|
||||
transformedResponse = processor.transform(existingscale, source);
|
||||
Assert.assertEquals("middle", transformedResponse);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransformByScaleLimits() throws TransformationException {
|
||||
String existingscale = "scale/limits.scale";
|
||||
|
||||
// Testing upper bound opened range
|
||||
String source = "500";
|
||||
String transformedResponse = processor.transform(existingscale, source);
|
||||
Assert.assertEquals("extreme", transformedResponse);
|
||||
|
||||
// Testing lower bound opened range
|
||||
source = "-10";
|
||||
transformedResponse = processor.transform(existingscale, source);
|
||||
Assert.assertEquals("low", transformedResponse);
|
||||
|
||||
// Testing unfinite up and down range
|
||||
existingscale = "scale/catchall.scale";
|
||||
source = "-10";
|
||||
transformedResponse = processor.transform(existingscale, source);
|
||||
Assert.assertEquals("catchall", transformedResponse);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransformByScaleUndef() throws TransformationException {
|
||||
// check that for undefined/non numeric value we return empty string
|
||||
// Issue #1107
|
||||
String existingscale = "scale/humidex_fr.scale";
|
||||
String source = "-";
|
||||
String transformedResponse = processor.transform(existingscale, source);
|
||||
Assert.assertEquals("", transformedResponse);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransformByScaleErrorInBounds() throws TransformationException {
|
||||
// the tested file contains inputs that generate a conversion error of the bounds
|
||||
// of range
|
||||
String existingscale = "scale/erroneous.scale";
|
||||
String source = "15";
|
||||
try {
|
||||
@SuppressWarnings("unused")
|
||||
String transformedResponse = processor.transform(existingscale, source);
|
||||
fail();
|
||||
} catch (TransformationException e) {
|
||||
// awaited result
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransformByScaleErrorInValue() throws TransformationException {
|
||||
// checks that an error is raised when trying to scale an erroneous value
|
||||
String existingscale = "scale/evaluationorder.scale";
|
||||
String source = "azerty";
|
||||
String transformedResponse = processor.transform(existingscale, source);
|
||||
Assert.assertEquals("", transformedResponse);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvaluationOrder() throws TransformationException {
|
||||
// Ensures that only first matching scale as presented in the file is taken in account
|
||||
String evaluationOrder = "scale/evaluationorder.scale";
|
||||
// This value matches two lines of the scale file
|
||||
String source = "12";
|
||||
|
||||
String transformedResponse = processor.transform(evaluationOrder, source);
|
||||
Assert.assertEquals("first", transformedResponse);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransformQuantityType() throws TransformationException {
|
||||
QuantityType<Dimensionless> airQuality = new QuantityType<>("992 ppm");
|
||||
String aqScaleFile = "scale/netatmo_aq.scale";
|
||||
String expected = "Correcte (992 ppm) !";
|
||||
|
||||
String transformedResponse = processor.transform(aqScaleFile, airQuality.toString());
|
||||
Assert.assertEquals(expected, transformedResponse);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCatchNonNumericValue() throws TransformationException {
|
||||
// checks that an error is raised when trying to scale an erroneous value
|
||||
String existingscale = "scale/catchnonnumeric.scale";
|
||||
String source = "azerty";
|
||||
String transformedResponse = processor.transform(existingscale, source);
|
||||
Assert.assertEquals("Non Numeric", transformedResponse);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransformAndFormat() throws TransformationException {
|
||||
String existingscale = "scale/netatmo_aq.scale";
|
||||
String source = "992";
|
||||
String transformedResponse = processor.transform(existingscale, source);
|
||||
Assert.assertEquals("Correcte (992) !", transformedResponse);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValueExceedsRange() throws TransformationException {
|
||||
String existingscale = "scale/humidex.scale";
|
||||
String source = "200";
|
||||
String transformedResponse = processor.transform(existingscale, source);
|
||||
Assert.assertEquals("", transformedResponse);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user