/**
* 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.dsmr.internal;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.List;
import java.util.Objects;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.dsmr.DSMRBindingProvider;
import org.openhab.binding.dsmr.internal.cosem.CosemValue;
import org.openhab.binding.dsmr.internal.messages.OBISMessage;
import org.openhab.binding.dsmr.internal.messages.OBISMsgFactory;
import org.openhab.binding.dsmr.internal.p1telegram.P1TelegramParser;
import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.types.State;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class periodically read out the P1 port of the Smart Meter.
* <p>
* The frequency is once every 10 seconds. This frequency is based on the update
* interval of the Smart Meter. The resulting values will be posted to the event
* bus.
* <p>
* At this moment the binding supports only a single Smart Meter.
* <p>
* The binding needs the following configuration parameters from openhab.cfg:
* <p>
* <ul>
* <li>dsmr.port (serial port device)
* <li>dsmr.<metertype>.chanel (M-Bus channel of the specified meter type gas,
* water, heating, cooling, generic, slaveelectricity)
* </ul>
* <p>
* The implementation of the binding is based on the Dutch Smart Meter
* Requirements (DSMR)
*
* @author M.Volaart
* @since 1.7.0
*/
public class DSMRBinding extends AbstractActiveBinding<DSMRBindingProvider> implements ManagedService {
/** Update interval as specified by DSMR */
public static final int DSMR_UPDATE_INTERVAL = 1000;
/* Logger */
private static final Logger logger = LoggerFactory.getLogger(DSMRBinding.class);
/* Serial port (configurable via openhab.cfg) */
private String port = "";
/* Fixed serial port speed settings (configurable via openhab.cfg) */
private DSMRPortSettings fixedPortSettings = null;
/* Meter - channel mapping (configurable via openhab.cfg) */
private final List<DSMRMeter> dsmrMeters = new ArrayList<DSMRMeter>();
/* DSMR Port object */
private DSMRPort dsmrPort;
/*
* the refresh interval which is used to poll values from the DSMR server
*
* Since we the device only updates every 10 seconds we let openHAB
* introduce a pause before execute is called again.
*
* We use here half the update interval time so we have some time to read
* the serial port (refreshInterval starts after previous executes ends)
*/
private long refreshInterval = DSMR_UPDATE_INTERVAL / 2;
/**
* Default Constructor
*/
public DSMRBinding() {
}
/**
* Activate the binding. We don't do anything special here.
* <p>
* To simplify synchronization issues we initiate the DSMR Port in the
* execute() method
*/
@Override
public void activate() {
logger.debug("Activate DSMRBinding");
}
/**
* Deactivates the binding.
* <p>
* This will close the DSMR Port
*/
@Override
public void deactivate() {
// deallocate resources here that are no longer needed and
// should be reset when activating this binding again
logger.info("Deactivating DSMRBinding");
if (dsmrPort != null) {
logger.debug("Closing DSMR port");
dsmrPort.close();
} else {
logger.info("DSMR port was not initialised");
}
}
/**
* @{inheritDoc
*/
@Override
protected long getRefreshInterval() {
return refreshInterval;
}
/**
* @{inheritDoc
*/
@Override
protected String getName() {
return "DSMR Binding";
}
/**
* @{inheritDoc
*/
@Override
protected void execute() {
// Check if there are any item bindings
if (!bindingsExist()) {
logger.debug("There is no existing DSMR binding configuration => refresh cycle aborted!");
return;
}
// Check if a valid DSMR port exists. Open a new one if necessary
if (dsmrPort == null || !dsmrPort.isOpen()) {
logger.debug("Creating DSMR Port: {}", port);
dsmrPort = new DSMRPort(port, new P1TelegramParser(new OBISMsgFactory(dsmrMeters)),
DSMR_UPDATE_INTERVAL / 2, DSMR_UPDATE_INTERVAL * 2, fixedPortSettings);
}
// Read the DSMRPort
List<OBISMessage> messages = dsmrPort.read();
logger.debug("Received {} messages", messages.size());
// Publish messages on the event bus
for (OBISMessage msg : messages) {
logger.debug("Read message: {}", msg);
for (DSMRBindingProvider provider : providers) {
for (String itemName : provider.getItemNames()) {
String dsmrItemId = provider.getDSMRItemID(itemName);
for (CosemValue<? extends State> openHABValue : msg.getOpenHABValues()) {
// DSMR items with an empty dsmrItemId are filtered
// automatically
if (dsmrItemId.equals(openHABValue.getDsmrItemId())) {
logger.debug("Publish data({}) to {}", dsmrItemId, itemName);
eventPublisher.postUpdate(itemName, openHABValue.getValue());
}
}
}
}
}
}
protected void addBindingProvider(DSMRBindingProvider bindingProvider) {
super.addBindingProvider(bindingProvider);
}
protected void removeBindingProvider(DSMRBindingProvider bindingProvider) {
super.removeBindingProvider(bindingProvider);
}
/**
* Read the dsmr:port and dsmr:<metertype>.channel properties
*/
@Override
public void updated(Dictionary<String, ?> config) throws ConfigurationException {
logger.debug("updated() is called!");
if (config != null) {
// Read port string
String portString = Objects.toString(config.get("port"), null);
logger.debug("dsmr:port={}", portString);
if (StringUtils.isNotBlank(portString)) {
port = portString;
} else {
logger.warn("dsmr:port setting is empty");
}
// Read port settings
String portSettingsString = (String) config.get("portsettings");
logger.debug("dsmr:portsettings={}", portSettingsString);
if (StringUtils.isNotBlank(portSettingsString)) {
fixedPortSettings = DSMRPortSettings.getPortSettingsFromString(portSettingsString);
} else {
logger.info("dsmr:portsettings setting is empty. Ignored");
}
/*
* Read the channel configuration
*/
dsmrMeters.clear();
for (DSMRMeterType meterType : DSMRMeterType.values()) {
String channelConfigValue = Objects.toString(config.get(meterType.channelConfigKey), null);
logger.debug("dsmr:{}={}", meterType.channelConfigKey, channelConfigValue);
if (StringUtils.isNotBlank(channelConfigValue)) {
try {
dsmrMeters.add(new DSMRMeter(meterType, Integer.parseInt(channelConfigValue)));
} catch (NumberFormatException nfe) {
logger.warn("Invalid value {} for dsmr:{}. Ignore mapping!", channelConfigValue,
meterType.channelConfigKey, nfe);
}
} else {
switch (meterType) {
case NA:
break; // Filter special DSMRMeterType
case ELECTRICITY:
break; // Always channel 0, configuration not needed
default:
logger.info("dsmr:{} setting is empty", meterType.channelConfigKey);
}
}
}
// Validate minimal configuration
if (port.length() > 0) {
logger.debug("Configuration succeeded");
setProperlyConfigured(true);
} else {
logger.debug("Configuration failed");
setProperlyConfigured(false);
}
}
}
}