/**
* 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.iec6205621meter.internal;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.bind.DatatypeConverter;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.iec6205621meter.Iec6205621MeterBindingProvider;
import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.items.Item;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.items.StringItem;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openmuc.j62056.DataSet;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* iec 62056-21 meter binding implementation
*
* @author Peter Kreutzer
* @author Günter Speckhofer
* @since 1.5.0
*/
public class Iec6205621MeterBinding extends AbstractActiveBinding<Iec6205621MeterBindingProvider>
implements ManagedService {
private static final Logger logger = LoggerFactory.getLogger(Iec6205621MeterBinding.class);
// regEx to validate a meter config
// <code>'^(.*?)\\.(serialPort|initMessage|baudRateChangeDelay|echoHandling)$'</code>
private final Pattern METER_CONFIG_PATTERN = Pattern
.compile("^(.*?)\\.(serialPort|initMessage|baudRateChangeDelay|echoHandling)$");
private static final long DEFAULT_REFRESH_INTERVAL = 60000;
/**
* the refresh interval which is used to poll values from the IEC 62056-21 Meter
* server (optional, defaults to 10 minutes)
*/
private long refreshInterval = DEFAULT_REFRESH_INTERVAL;
// configured meter devices - keyed by meter device name
private final Map<String, Meter> meterDeviceConfigurtions = new HashMap<String, Meter>();
public Iec6205621MeterBinding() {
}
@Override
public void activate() {
}
@Override
public void deactivate() {
meterDeviceConfigurtions.clear();
}
/**
* @{inheritDoc
*/
@Override
protected long getRefreshInterval() {
return refreshInterval;
}
/**
* @{inheritDoc
*/
@Override
protected String getName() {
return "iec6205621meter Refresh Service";
}
private final Meter createIec6205621MeterConfig(String name, MeterConfig config) {
Meter reader = null;
reader = new Meter(name, config);
return reader;
}
/**
* @{inheritDoc
*/
@Override
protected void execute() {
// the frequently executed code (polling) goes here ...
Map<String, Map<String, DataSet>> cache = new HashMap<String, Map<String, DataSet>>();
for (Iec6205621MeterBindingProvider provider : providers) {
for (String itemName : provider.getItemNames()) {
for (Entry<String, Meter> entry : meterDeviceConfigurtions.entrySet()) {
Meter reader = entry.getValue();
String meterName = provider.getMeterName(itemName);
if (meterName != null && meterName.equals(entry.getKey())) {
Map<String, DataSet> dataSets;
if ((dataSets = cache.get(meterName)) == null) {
if (logger.isDebugEnabled()) {
logger.debug("Read meter: {}; {}", meterName, reader.getConfig().getSerialPort());
}
dataSets = reader.read();
cache.put(meterName, dataSets);
}
String obis = provider.getObis(itemName);
if (obis != null && dataSets.containsKey(obis)) {
DataSet dataSet = dataSets.get(obis);
if (logger.isDebugEnabled()) {
logger.debug("Updating item {} with OBIS code {} and value {}", itemName, obis,
dataSet.getValue());
}
Class<? extends Item> itemType = provider.getItemType(itemName);
if (itemType.isAssignableFrom(NumberItem.class)) {
eventPublisher.postUpdate(itemName, new DecimalType(dataSet.getValue()));
}
if (itemType.isAssignableFrom(StringItem.class)) {
String value = dataSet.getValue();
eventPublisher.postUpdate(itemName, new StringType(value));
}
}
}
}
}
}
}
/**
* @{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'.
}
/**
* @{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'.
}
protected void addBindingProvider(Iec6205621MeterBindingProvider bindingProvider) {
super.addBindingProvider(bindingProvider);
}
protected void removeBindingProvider(Iec6205621MeterBindingProvider bindingProvider) {
super.removeBindingProvider(bindingProvider);
}
/**
* {@inheritDoc}
*/
@Override
public void updated(Dictionary<String, ?> config) throws ConfigurationException {
if (config == null || config.isEmpty()) {
return;
}
Set<String> names = getNames(config);
for (String name : names) {
String value = Objects.toString(config.get(name + ".serialPort"), null);
String serialPort = value != null ? value : MeterConfig.DEFAULT_SERIAL_PORT;
value = Objects.toString(config.get(name + ".initMessage"), null);
byte[] initMessage = value != null ? DatatypeConverter.parseHexBinary(value) : null;
value = Objects.toString(config.get(name + ".baudRateChangeDelay"), null);
int baudRateChangeDelay = value != null ? Integer.valueOf(value)
: MeterConfig.DEFAULT_BAUD_RATE_CHANGE_DELAY;
value = Objects.toString(config.get(name + ".echoHandling"), null);
boolean echoHandling = value != null ? Boolean.valueOf(value) : MeterConfig.DEFAULT_ECHO_HANDLING;
Meter meterConfig = createIec6205621MeterConfig(name,
new MeterConfig(serialPort, initMessage, baudRateChangeDelay, echoHandling));
if (meterDeviceConfigurtions.put(meterConfig.getName(), meterConfig) != null) {
logger.info("Recreated reader {} with {}!", meterConfig.getName(), meterConfig.getConfig());
} else {
logger.info("Created reader {} with {}!", meterConfig.getName(), meterConfig.getConfig());
}
}
// to override the default refresh interval one has to add a
// parameter to openhab.cfg like
// <bindingName>:refresh=<intervalInMs>
String refreshStr = Objects.toString(config.get("refresh"), null);
if (StringUtils.isNotBlank(refreshStr)) {
refreshInterval = Long.parseLong(refreshStr);
}
setProperlyConfigured(true);
}
/**
* Analyze configuration to get meter names
*
* @return set of String of meter names
*/
private Set<String> getNames(Dictionary<String, ?> config) {
Set<String> set = new HashSet<String>();
Enumeration<String> keys = config.keys();
while (keys.hasMoreElements()) {
String key = keys.nextElement();
// the config-key enumeration contains additional keys that we
// don't want to process here ...
if ("service.pid".equals(key) || "refresh".equals(key)) {
continue;
}
Matcher meterMatcher = METER_CONFIG_PATTERN.matcher(key);
if (!meterMatcher.matches()) {
logger.debug(
"given config key '{}' does not follow the expected pattern '<meterName>.<serialPort|baudRateChangeDelay|echoHandling>'",
key);
continue;
}
meterMatcher.reset();
meterMatcher.find();
set.add(meterMatcher.group(1));
}
return set;
}
}