/**
* Copyright (c) 2010-2016 by the respective copyright holders.
*
* 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.smarthomatic.internal;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.smarthomatic.SmarthomaticBindingProvider;
import org.openhab.binding.smarthomatic.internal.SHCMessage.SHCData;
import org.openhab.binding.smarthomatic.internal.SHCMessage.SHCHeader;
import org.openhab.binding.smarthomatic.internal.packetData.Packet;
import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.items.Item;
import org.openhab.core.library.types.DecimalType;
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.osgi.framework.Bundle;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* These class processes data that is send on the event bus and destined for a
* smarthomatic device. The data is parsed and the put into smarthomatic data
* structures. Likewise if received data is to be send on the event bus
*
* @author arohde
* @author mcjobo
* @since 1.9.0
*/
public class SmarthomaticBinding extends AbstractActiveBinding<SmarthomaticBindingProvider>
implements ManagedService, SerialEventWorker {
private static final Logger logger = LoggerFactory.getLogger(SmarthomaticBinding.class);
/**
* RegEx to extract a parse a function String <code>'(.*?)\((.*)\)'</code>
*/
private static final Pattern EXTRACT_FUNCTION_PATTERN = Pattern.compile("(.*?)\\((.*)\\)");
private BaseStation baseStation;
private String serialPortname;
private int serialBaudrate;
private Packet packet;
/**
* the refresh interval which is used to poll values from the Smarthomatic
* server (optional, defaults to 60000ms)
*
*/
private long refreshInterval = 60000;
public SmarthomaticBinding() {
}
/**
* activate binding
*
*/
@Override
public void activate() {
// log activate of binding
if (baseStation != null) {
logger.info("Smarthomatic Binding activated. BaseStation= {}", baseStation.toString());
}
Bundle bundle = SmarthomaticActivator.getContext().getBundle();
URL fileURL = bundle.getEntry("packet_layout.xml");
Packet packet = null;
try {
InputStream inputStream = fileURL.openConnection().getInputStream();
JAXBContext jaxbContext = JAXBContext.newInstance(Packet.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
packet = (Packet) jaxbUnmarshaller.unmarshal(inputStream);
} catch (IOException e1) {
e1.printStackTrace();
} catch (JAXBException e) {
e.printStackTrace();
}
this.packet = packet;
}
/**
* deactivate binding
*
*/
@Override
public void deactivate() {
// deallocate resources here that are no longer needed and
// should be reset when activating this binding again
}
/**
* @{inheritDoc
*
*/
@Override
protected long getRefreshInterval() {
return refreshInterval;
}
/**
* @{inheritDoc
*
*/
@Override
protected String getName() {
return "Smarthomatic Refresh Service";
}
/**
* @{inheritDoc
*
*/
@Override
protected void execute() {
// the frequently executed code (polling) goes here ...
// logger.debug("execute() method is called!");
}
/**
* @{inheritDoc
*
*/
@Override
protected void internalReceiveCommand(String itemName, Command command) {
// the code being executed when a command was sent on the openHAB
// event bus goes here. This method is only called if one of the
// BindingProviders provide a binding for the given 'itemName'.
for (SmarthomaticBindingProvider provider : this.providers) {
if (provider.providesBindingFor(itemName)) {
if (baseStation != null) {
baseStation.sendCommand(provider.getDeviceId(itemName), provider.getMessageGroupId(itemName),
provider.getMessageId(itemName), 0, command);
// provider.getType(itemName),
// provider.getToggleTime(itemName), command);
}
}
}
logger.debug("internalReceiveCommand() is called! {},{}", new String[] { itemName, command.toString() });
}
/**
* @{inheritDoc
*
*/
@Override
protected void internalReceiveUpdate(String itemName, State newState) {
// the code being executed when a state was sent on the openHAB
// event bus goes here. This method is only called if one of the
// BindingProviders provide a binding for the given 'itemName'.
logger.debug("internalReceiveUpdate() is called!{},{}", new String[] { itemName, newState.toString() });
}
/**
* @{inheritDoc
*
*/
@Override
public void updated(Dictionary<String, ?> config) throws ConfigurationException {
if (config != null) {
// to override the default refresh interval one has to add a
// parameter to openhab.cfg like
// <bindingName>:refresh=<intervalInMs>
String refreshIntervalString = (String) config.get("refresh");
if (StringUtils.isNotBlank(refreshIntervalString)) {
refreshInterval = Long.parseLong(refreshIntervalString);
}
boolean changed = false;
if (serialPortname != (String) config.get("serialPort")) {
serialPortname = (String) config.get("serialPort");
changed = true;
}
String dummy = (String) config.get("baud");
try {
if (serialBaudrate != Integer.parseInt(dummy)) {
serialBaudrate = Integer.parseInt(dummy);
changed = true;
}
} catch (NumberFormatException e) {
logger.info("reading exception");
}
if (changed | (baseStation == null)) {
if (baseStation != null) {
baseStation.closeSerialPort();
}
baseStation = new BaseStation(serialPortname, serialBaudrate, this);
logger.debug("Smarthomatic Binding:update creates new basestation");
}
Enumeration<String> keys = config.keys();
for (int i = 0; i < config.size(); i++) {
String key = keys.nextElement();
StringTokenizer tokens = new StringTokenizer(key, ":");
if (tokens.nextToken().equals("device")) {
if (tokens.hasMoreElements()) {
dummy = tokens.nextToken();
int deviceID = Integer.parseInt(dummy);
String name = (String) config.get(key);
SmarthomaticGenericBindingProvider.addDevice(name, deviceID);
logger.debug("Smarthomatic device {} can be indexed by name {}", new String[] { dummy, name });
}
}
logger.debug("KEY: {}", key);
}
setProperlyConfigured(true);
}
}
private String processTransformation(String transformation, String response) {
String transformedResponse = response;
if (transformation == null) {
return transformedResponse;
}
try {
String[] parts = splitTransformationConfig(transformation);
String transformationType = parts[0];
String transformationFunction = parts[1];
TransformationService transformationService = TransformationHelper
.getTransformationService(SmarthomaticActivator.getContext(), transformationType);
if (transformationService != null) {
transformedResponse = transformationService.transform(transformationFunction, response);
} else {
transformedResponse = response;
logger.warn("couldn't transform response because transformationService of type '{}' is unavailable",
transformationType);
}
} catch (TransformationException te) {
logger.error("transformation throws exception [transformation= {}, response= {}]", transformation,
response);
logger.error("received transformation exception", te);
// in case of an error we return the response without any
// transformation
transformedResponse = response;
}
logger.debug("transformed response is '{}'", transformedResponse);
return transformedResponse;
}
/**
* Splits a transformation configuration string into its two parts - the
* transformation type and the function/pattern to apply.
*
* @param transformation
* the string to split
* @return a string array with exactly two entries for the type and the
* function
*/
protected String[] splitTransformationConfig(String transformation) {
Matcher matcher = EXTRACT_FUNCTION_PATTERN.matcher(transformation);
if (!matcher.matches()) {
throw new IllegalArgumentException("given transformation function '" + transformation
+ "' does not follow the expected pattern '<function>(<pattern>)'");
}
matcher.reset();
matcher.find();
String type = matcher.group(1);
String pattern = matcher.group(2);
return new String[] { type, pattern };
}
@Override
public void eventOccured(String message) {
StringTokenizer strTok = new StringTokenizer(message, "\n");
String data = null;
// check incoming data
int i = 0;
while (strTok.hasMoreTokens()) {
String s = strTok.nextToken();
if (s.contains(SHCMessage.DATA_FLAG)) {
data = s;
logger.debug("<BaseStation data>[{}]: {}", i, data);
}
}
if (data != null) {
SHCMessage shcMessage = new SHCMessage(data, packet);
SHCHeader shcHeader = shcMessage.getHeader();
logger.debug("BaseStation SenderID: {} MsgType: {} MsgGroupID: {} MsgID: {} MsgData: {}",
shcHeader.getSenderID(), shcHeader.getMessageType(), shcHeader.getMessageGroupID(),
shcHeader.getMessageID(), shcHeader.getMessageData());
SHCData info = shcMessage.getData();
if (info != null) {
logger.debug(info.toString());
}
// search all matching providers where DeviceID == SenderID of
// Message-Header
for (SmarthomaticBindingProvider provider : this.providers) {
for (String itemName : provider.getItemNames()) {
if (shcHeader.getSenderID() == provider.getDeviceId(itemName)
&& shcHeader.getMessageGroupID() == provider.getMessageGroupId(itemName)
&& shcHeader.getMessageID() == provider.getMessageId(itemName)) {
Type type = shcMessage.openHABStateFromSHCMessage(provider.getItem(itemName))
.get(provider.getMessagePartId(itemName));
String transformed = processTransformation(provider.getConfigParam(itemName, "transformation"),
type.toString());
if (type instanceof DecimalType) {
type = DecimalType.valueOf(transformed);
}
if (isDataTypeSupported(provider.getItem(itemName), type)) {
eventPublisher.postUpdate(itemName, (State) type);
}
}
}
}
}
}
private boolean isDataTypeSupported(Item item, Type type) {
boolean result = false;
for (Class<? extends State> supportedState : item.getAcceptedDataTypes()) {
if (supportedState.isAssignableFrom(type.getClass())) {
result = true;
}
}
return result;
}
}