Skip to content
This repository has been archived by the owner on May 17, 2021. It is now read-only.

[Serial binding] OH2 Compatibility issue with RegexTransformation service #5184

Merged
merged 5 commits into from May 23, 2017
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -17,5 +17,4 @@
</service>
<reference bind="setEventPublisher" cardinality="1..1" interface="org.openhab.core.events.EventPublisher" name="EventPublisher" policy="dynamic" unbind="unsetEventPublisher"/>
<property name="event.topics" type="String" value="openhab/command/*"/>
<reference bind="setTransformationService" cardinality="0..1" interface="org.openhab.core.transform.TransformationService" name="TransformationService" policy="dynamic" target="(openhab.transform=REGEX)" unbind="unsetTransformationService"/>
</scr:component>
3 changes: 2 additions & 1 deletion bundles/binding/org.openhab.binding.serial/README.md
Expand Up @@ -63,7 +63,7 @@ where:

* `<port>` is the identification of the serial port on the host system, e.g. `COM1` on Windows, `/dev/ttyS0` on Linux or `/dev/tty.PL2303-0000103D` on Mac. The same `<port>` can be bound to multiple items.
* `<baudrate>` is the baud rate of the port. Backward compatibility is given; if no baud rate is specified the serial binding defaults to 9600 baud.
* `REGEX(<regular expression>)` allows parsing for special strings or numbers in the serial stream. This is based on the [RegEx Service](https://github.com/openhab/openhab1-addons/wiki/Transformations#regex-transformation-service). This is optional.
* `REGEX(<regular expression>)` allows parsing for special strings or numbers in the serial stream. You can use a capture group (e.g. REGEX(Position:([0-9.]*)) to capture 12 in "Position:12" or substitution (e.g. REGEX(s/Position:100/ON/) or REGEX(s/Position:100/ON/g)) to replace (FIRST or ALL) "Position:100" strings in response with "ON". This is based on the [RegEx Service](https://github.com/openhab/openhab1-addons/wiki/Transformations#regex-transformation-service) and [ESH RegExTransformationService](https://github.com/eclipse/smarthome/tree/master/extensions/transform/org.eclipse.smarthome.transform.regex). This is optional.
* `BASE64` enables the Base64 mode. With this mode all data received on the serial port is saved in Base64 format. In this mode also all data that is sent to the serial port has to be Base64 encoded. (This was implemented because some serial devices are using bytes that are not supported by the REST interface).
* `ON(<On string>),OFF(<Off string>)` if used in conjunction with a Switch this mapping will send specific commands to serial port and also match a serial command to specific ON/OFF state. This way you don't have to use a rule to send a command to serial
* `UP(<Up string>),DOWN(<Down string>),STOP(<Stop string>)` if used in conjunction with a Rollershutter this mapping will send specific commands to serial port. Use REGEX to parse Rollershutter postion (0-100%) comming as feedback over serial link
Expand All @@ -84,4 +84,5 @@ String AVR "Surround System" (Multimedia) { serial="
Number Temperature "My Temp. Sensor" (Weather) { serial="/dev/ttyS1@115200,REGEX(ID:2.*,T:([0-9.]*))" }
Switch SerialRelay "Relay Q1" (Entrance) { serial="/dev/ttyS0,ON(Q1_ON\n),OFF(Q1_OFF\n)" }
Rollershutter SerialRollo "Entrance Rollo" (Entrance) { serial="/dev/ttyS0,REGEX(Position:([0-9.]*)),UP(Rollo_UP\n),DOWN(Rollo_Down\n),STOP(Rollo_Stop\n)" }
Switch RoloAt100 "Rolo at 100" (Entrance) { serial="/dev/ttyS0,REGEX(s/Position:100/ON/)" }
```
@@ -0,0 +1,130 @@
/**
* Copyright (c) 2010-2016 by the respective copyright holders.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2017

*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.binding.serial.internal;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.openhab.core.transform.TransformationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Pattern matcher to match and cache patterns
*
* @author Marek Halmo
* @see RegexPatternMatcher#getMatches getMatches()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add an "@SInCE 1.10.0" below "@author"

*
*/
public class RegexPatternMatcher {
private static final Logger logger = LoggerFactory.getLogger(RegexPatternMatcher.class);
private static HashMap<String, Pattern> patternCache = new HashMap<String, Pattern>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new HashMap<>();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point

private static final Pattern SUBST_PATTERN = Pattern.compile("^s/(.*?[^\\\\])/(.*?[^\\\\])/(.*)$");

/**
* Returns compiled pattern for specific regeExpression that is stored in patternCache
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"regeExpression" should say "regExpression", or possibly "regular expression".

*
* @param regExpression
* @return
*/
private static Pattern getPattern(String regExpression) {
cache(regExpression);
return patternCache.get(regExpression);
}

/**
* Returns true if given regExpression is a substitution pattern
* This fact is indicated by giving
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"indicated by giving" ... what? Please complete this sentence.

*
* @param regExpression
* @return
*/
private static boolean isSubtitution(String regExpression) {
cache(regExpression);
return patternCache.get(regExpression) == null;
}

/**
* Caches given regexpression, if the expression is substitution pattern it marks is as substitution
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Caches given regExpression. If the expression is a substitution pattern, it marks it as a substitution."

*
* @return
*/
private static void cache(String regExpression) {
if (!patternCache.containsKey(regExpression)) {
// Check if this pattern is a substitution. If so ignore it
Matcher substMatcher = SUBST_PATTERN.matcher(regExpression);
if (substMatcher.matches()) {
patternCache.put(regExpression, null);
} else {
Pattern pattern = Pattern.compile(regExpression, Pattern.DOTALL);
patternCache.put(regExpression, pattern);
}
}
}

/**
* Returns array of strings that match given regular expression.
* If expression is not known the compiled pattern is cached in pattern cache
*
* @param regExpression regular expression to match or a substitution in form of "s/<regex>/result/g" (replace all)
* or "s/<regex>/result/" (replace first)
* @param source text to search in
* @return
* @throws TransformationException if regExpression or source is null
* @see org.eclipse.smarthome.transform.regex.internal.RegExTranformationService RegExTranformationService
*/
public static String[] getMatches(String regExpression, String source) throws TransformationException {
if (regExpression == null || source == null) {
throw new TransformationException("the given parameters 'regex' and 'source' must not be null");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"The given" ...

}

logger.debug("about to transform '{}' by the function '{}'", source, regExpression);

// Check if RegEx is a substitution (s/<regex>/result/g) or (s/<regex>/result/)
if (isSubtitution(regExpression)) {
Matcher substMatcher = SUBST_PATTERN.matcher(regExpression);
if (substMatcher.matches()) {
logger.debug("Using substitution form of regex transformation");
String regex = substMatcher.group(1);
String substitution = substMatcher.group(2);
String options = substMatcher.group(3);

String result;
if (options.equals("g")) {
result = source.trim().replaceAll(regex, substitution);
} else {
result = source.trim().replaceFirst(regex, substitution);
}

return new String[] { result };
}
}

Matcher matcher = getPattern(regExpression).matcher(source.trim());
List<String> results = new ArrayList<String>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

= new ArrayList<>();


while (matcher.find()) {
results.add(matcher.group(1));
}

return results.toArray(new String[results.size()]);
}

/**
* Removes pattern from cache
*
* @param regExpression
*/
public static void removePattern(String regExpression) {
patternCache.remove(regExpression);
}
}
Expand Up @@ -24,7 +24,6 @@
import org.openhab.core.library.types.StopMoveType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.types.UpDownType;
import org.openhab.core.transform.TransformationService;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.model.item.binding.BindingConfigParseException;
Expand Down Expand Up @@ -69,8 +68,6 @@ public class SerialBinding extends AbstractEventSubscriber implements BindingCon

private EventPublisher eventPublisher = null;

private TransformationService transformationService;

public void setEventPublisher(EventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;

Expand All @@ -87,20 +84,6 @@ public void unsetEventPublisher(EventPublisher eventPublisher) {
}
}

public void setTransformationService(TransformationService transformationService) {
this.transformationService = transformationService;
for (SerialDevice serialDevice : serialDevices.values()) {
serialDevice.setTransformationService(transformationService);
}
}

public void unsetTransformationService(TransformationService transformationService) {
this.transformationService = null;
for (SerialDevice serialDevice : serialDevices.values()) {
serialDevice.setTransformationService(null);
}
}

/**
* {@inheritDoc}
*/
Expand Down Expand Up @@ -174,31 +157,39 @@ public void processBindingConfiguration(String context, Item item, String bindin
String downCommand = null;
String stopCommand = null;

String[] split = bindingConfig.split(",");
if (split.length > 0) {
for (int i = 1; i < split.length; i++) {
String part = split[i];
String substring = part.substring(0, part.length());

if (substring.startsWith("REGEX(")) {
pattern = substring.substring(6, substring.length() - 1);
} else if (substring.equals("BASE64")) {
base64 = true;
} else if (substring.startsWith("ON(")) {
onCommand = substring.substring(3, substring.length() - 1);
} else if (substring.startsWith("OFF(")) {
offCommand = substring.substring(4, substring.length() - 1);
} else if (substring.startsWith("UP(")) {
upCommand = substring.substring(3, substring.length() - 1);
} else if (substring.startsWith("DOWN(")) {
downCommand = substring.substring(5, substring.length() - 1);
} else if (substring.startsWith("STOP(")) {
stopCommand = substring.substring(5, substring.length() - 1);
int parameterSplitterAt = bindingConfig.indexOf(",");

if (parameterSplitterAt > 0) {
String[] split = bindingConfig.substring(parameterSplitterAt + 1, bindingConfig.length() - 1).split("\\),");
if (split.length > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant. Should remove this if block.

for (int i = 0; i < split.length; i++) {
String substring = split[i];

if (substring.startsWith("REGEX(")) {
pattern = substring.substring(6, substring.length());
} else if (substring.equals("BASE64")) {
base64 = true;
} else if (substring.startsWith("ON(")) {
onCommand = substring.substring(3, substring.length());
} else if (substring.startsWith("OFF(")) {
offCommand = substring.substring(4, substring.length());
} else if (substring.startsWith("UP(")) {
upCommand = substring.substring(3, substring.length());
} else if (substring.startsWith("DOWN(")) {
downCommand = substring.substring(5, substring.length());
} else if (substring.startsWith("STOP(")) {
stopCommand = substring.substring(5, substring.length());
}
}
}
}

String portConfig[] = split[0].split("@");
String portConfig[];
if (parameterSplitterAt > 0) {
portConfig = bindingConfig.substring(0, parameterSplitterAt).split("@");
} else {
portConfig = bindingConfig.split("@");
}

String port = portConfig[0];
int baudRate = 0;
Expand All @@ -215,7 +206,6 @@ public void processBindingConfiguration(String context, Item item, String bindin
serialDevice = new SerialDevice(port);
}

serialDevice.setTransformationService(transformationService);
serialDevice.setEventPublisher(eventPublisher);
try {
serialDevice.initialize();
Expand Down
Expand Up @@ -29,7 +29,6 @@
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.transform.TransformationException;
import org.openhab.core.transform.TransformationService;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -42,7 +41,7 @@
import gnu.io.UnsupportedCommOperationException;

/**
* This class represents a serial device that is linked to exactly one String item and/or Switch item.
* This class represents a serial device that is linked to one or many String, Number, Switch or Rollershutter items
*
* @author Kai Kreuzer
*
Expand All @@ -55,7 +54,6 @@ public class SerialDevice implements SerialPortEventListener {
private int baud = 9600;

private EventPublisher eventPublisher;
private TransformationService transformationService;

private CommPortIdentifier portId;
private SerialPort serialPort;
Expand Down Expand Up @@ -102,6 +100,12 @@ public void addConfig(String itemName, Class<?> type, String pattern, boolean ba

public void removeConfig(String itemName) {
if (configMap != null) {
ItemType type = configMap.get(itemName);
if (type.pattern != null) {
// For duplicate patterns - it will be added to cache next time it is required
RegexPatternMatcher.removePattern(type.pattern);
}

configMap.remove(itemName);
}
}
Expand All @@ -123,10 +127,6 @@ public void unsetEventPublisher(EventPublisher eventPublisher) {
this.eventPublisher = null;
}

public void setTransformationService(TransformationService transformationService) {
this.transformationService = transformationService;
}

public String getPort() {
return port;
}
Expand Down Expand Up @@ -280,39 +280,35 @@ public void serialEvent(SerialPortEvent event) {
if (eventPublisher != null) {
if (configMap != null && !configMap.isEmpty()) {
for (Entry<String, ItemType> entry : configMap.entrySet()) {

String pattern = entry.getValue().pattern;
// use pattern
if (entry.getValue().pattern != null) {

if (transformationService == null) {
logger.error("No transformation service available!");
if (pattern != null) {
try {
String[] matches = RegexPatternMatcher.getMatches(pattern, result);

} else {
try {
String value = transformationService.transform(entry.getValue().pattern,
result);
for (int i = 0; i < matches.length; i++) {
String match = matches[i];

try {
State state = null;

if (entry.getValue().type.equals(NumberItem.class)) {
state = new DecimalType(value);
state = new DecimalType(match);
} else if (entry.getValue().type == RollershutterItem.class) {
state = new PercentType(value);
state = new PercentType(match);
} else {
state = new StringType(value);
state = new StringType(match);
}

eventPublisher.postUpdate(entry.getKey(), state);
} catch (NumberFormatException e) {
logger.warn("Unable to convert regex result '{}' for item {} to number",
new String[] { result, entry.getKey() });
}
} catch (TransformationException e) {
logger.error("Unable to transform!", e);
}
} catch (TransformationException e) {
logger.error("Unable to transform!", e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a warn instead of an error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not shure, TransformationException is thrown only if you provide null pattern or result.
If the transformation fails due to some regex matching, it will be properly shown as a warning logger.warn("Unable to convert ....

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the logging guidelines.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, based on guidelines you are right.. changing to warn

}

} else if (entry.getValue().type == StringItem.class) {
if (entry.getValue().base64) {
result = Base64.encodeBase64String(result.getBytes());
Expand Down