/**
* 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.stiebelheatpump.internal;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.stiebelheatpump.StiebelHeatPumpBindingProvider;
import org.openhab.binding.stiebelheatpump.protocol.ProtocolConnector;
import org.openhab.binding.stiebelheatpump.protocol.Request;
import org.openhab.binding.stiebelheatpump.protocol.SerialPortConnector;
import org.openhab.binding.stiebelheatpump.protocol.TcpConnector;
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.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Stiebel heat pump binding implementation.
*
* @author Peter Kreutzer
* @since 1.5.0
*/
public class StiebelHeatPumpBinding extends AbstractActiveBinding<StiebelHeatPumpBindingProvider>
implements ManagedService {
private static final Logger logger = LoggerFactory.getLogger(StiebelHeatPumpBinding.class);
// configuration defaults for optional properties
private static int DEFAULT_BAUD_RATE = 9600;
private static int DEFAULT_SERIAL_TIMEOUT = 5;
/**
* the refresh interval which is used to poll values from the heat pump
* server (optional, defaults to 1 Minute)
*/
private long refreshInterval = 60000; // in ms
/** timeout for the serial port */
private int serialTimeout = DEFAULT_SERIAL_TIMEOUT;
/** version number of heat pump */
private String version = "";
/** heat pump request definition */
private List<Request> heatPumpConfiguration = new ArrayList<Request>();
// ** request to get the version of the heat pump
Request versionRequest;
// ** indicates if the communication is currently in use by a call
boolean communicationInUse = false;
private ProtocolConnector connector;
public StiebelHeatPumpBinding() {
}
@Override
public void activate() {
}
@Override
public void deactivate() {
}
/**
* @{inheritDoc
*/
@Override
protected long getRefreshInterval() {
return refreshInterval;
}
/**
* @{inheritDoc
*/
@Override
protected String getName() {
return "stiebelheatpump Refresh Service";
}
/**
* @{inheritDoc
*/
@Override
protected void execute() {
if (communicationInUse) {
return;
}
logger.debug("Refresh heat pump sensor and status values ...");
communicationInUse = true;
CommunicationService communicationService = null;
try {
communicationService = new CommunicationService(connector, heatPumpConfiguration);
Map<String, String> data = new HashMap<String, String>();
Map<String, String> allData = new HashMap<String, String>();
data = communicationService.getStatus();
allData.putAll(data);
for (Map.Entry<String, String> entry : data.entrySet()) {
logger.debug("Data {} has value {}", entry.getKey(), entry.getValue());
}
data = communicationService.getSensors();
allData.putAll(data);
for (Map.Entry<String, String> entry : data.entrySet()) {
logger.debug("Data {} has value {}", entry.getKey(), entry.getValue());
}
publishValues(allData);
} catch (StiebelHeatPumpException e) {
logger.error("Could not read data from heat pump! " + e.toString());
} finally {
communicationService.finalizer();
communicationInUse = false;
}
}
/**
* @{inheritDoc
*/
@Override
protected void internalReceiveCommand(String itemName, Command command) {
logger.debug("Received command {} for item {}", command, itemName);
int retry = 0;
while (communicationInUse & (retry < DEFAULT_SERIAL_TIMEOUT)) {
try {
Thread.sleep(CommunicationService.WAITING_TIME_BETWEEN_REQUESTS);
} catch (InterruptedException e) {
logger.debug("Could not get access to heatpump ! : {}", e.toString());
}
retry++;
}
if (communicationInUse) {
return;
}
for (StiebelHeatPumpBindingProvider provider : providers) {
for (String name : provider.getItemNames()) {
if (!name.equals(itemName)) {
continue;
}
String parameter = provider.getParameter(name);
logger.debug("Found item {} with heat pump parameter {} in providers", itemName, parameter);
try {
Map<String, String> data = new HashMap<String, String>();
communicationInUse = true;
CommunicationService communicationService = new CommunicationService(connector,
heatPumpConfiguration);
data = communicationService.setData(command.toString(), parameter);
communicationService.finalizer();
publishValues(data);
} catch (StiebelHeatPumpException e) {
logger.error("Could not set new value!");
} finally {
communicationInUse = false;
}
}
}
}
/**
* @{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!");
// logger.debug("Received update {} for item {}", newState, itemName);
}
protected void addBindingProvider(StiebelHeatPumpBindingProvider bindingProvider) {
super.addBindingProvider(bindingProvider);
}
protected void removeBindingProvider(StiebelHeatPumpBindingProvider bindingProvider) {
super.removeBindingProvider(bindingProvider);
}
/**
* {@inheritDoc}
*/
@Override
public void updated(Dictionary<String, ?> config) throws ConfigurationException {
String serialPort = null;
int baudRate = DEFAULT_BAUD_RATE;
serialTimeout = DEFAULT_SERIAL_TIMEOUT;
String host = null;
int port = 0;
logger.debug("Loading stiebelheatpump binding configuration.");
if (config == null || config.isEmpty()) {
logger.warn("Empty or null configuration. Ignoring.");
return;
}
if (config != null) {
// to override the default refresh interval one has to add a
// parameter to openhab.cfg like
// <bindingName>:refresh=<intervalInMs>
if (StringUtils.isNotBlank((String) config.get("refresh"))) {
refreshInterval = Long.parseLong((String) config.get("refresh"));
}
if (StringUtils.isNotBlank((String) config.get("serialPort"))) {
serialPort = (String) config.get("serialPort");
}
if (StringUtils.isNotBlank((String) config.get("baudRate"))) {
baudRate = Integer.parseInt((String) config.get("baudRate"));
}
if (StringUtils.isNotBlank((String) config.get("host"))) {
host = (String) config.get("host");
}
if (StringUtils.isNotBlank((String) config.get("port"))) {
port = Integer.parseInt((String) config.get("port"));
}
if (StringUtils.isNotBlank((String) config.get("serialTimeout"))) {
serialTimeout = Integer.parseInt((String) config.get("serialTimeout"));
}
if (StringUtils.isNotBlank((String) config.get("version"))) {
version = (String) config.get("version");
}
try {
if (host != null) {
this.connector = new TcpConnector(host, port);
} else {
this.connector = new SerialPortConnector(serialPort, baudRate);
}
boolean isInitialized = getInitialHeatPumpSettings();
setTime();
if (host != null) {
logger.info("Created heatpump configuration with tcp {}:{}, version:{} ", host, port, version);
} else {
logger.info("Created heatpump configuration with serialport:{}, baudrate:{}, version:{} ",
serialPort, baudRate, version);
}
setProperlyConfigured(isInitialized);
} catch (RuntimeException e) {
logger.warn(e.getMessage(), e);
throw e;
}
}
}
/**
* This method reads initially all information from the heat pump It read
* the configuration file and loads all defined record definitions of sensor
* data, status information , actual time settings and setting parameter
* values.
*
* @return true if heat pump information could be successfully read
*/
public boolean getInitialHeatPumpSettings() {
CommunicationService communicationService = null;
try {
int retry = 0;
while (communicationInUse) {
try {
Thread.sleep(CommunicationService.WAITING_TIME_BETWEEN_REQUESTS);
retry++;
if (retry > DEFAULT_SERIAL_TIMEOUT) {
return false;
}
} catch (InterruptedException e) {
logger.error("could not access Heat pump for version {}", version);
}
}
communicationInUse = true;
communicationService = new CommunicationService(connector);
Map<String, String> data = new HashMap<String, String>();
Map<String, String> allData = new HashMap<String, String>();
heatPumpConfiguration = communicationService.getHeatPumpConfiguration(version + ".xml");
String version = communicationService.getversion();
logger.info("Heat pump has version {}", version);
allData.put("Version", version);
data = communicationService.getSettings();
allData.putAll(data);
data = communicationService.getStatus();
allData.putAll(data);
data = communicationService.getSensors();
allData.putAll(data);
for (Map.Entry<String, String> entry : allData.entrySet()) {
logger.debug("Data {} has value {}", entry.getKey(), entry.getValue());
}
publishValues(allData);
return true;
} catch (StiebelHeatPumpException e) {
logger.error("Stiebel heatpump version could not be read from heat pump! " + e.toString());
} finally {
communicationInUse = false;
if (communicationService != null) {
communicationService.finalizer();
}
}
return false;
}
/**
* This method sets the time in the heat pump.
* I case of the time the time is initially verified and set to
* actual time.
*
* @return true if heat pump time could be successfully set
*/
public boolean setTime() {
CommunicationService communicationService = null;
try {
int retry = 0;
while (communicationInUse) {
try {
Thread.sleep(CommunicationService.WAITING_TIME_BETWEEN_REQUESTS);
retry++;
if (retry > DEFAULT_SERIAL_TIMEOUT) {
return false;
}
} catch (InterruptedException e) {
logger.error("could not access Heat pump for has version {}", version);
}
}
communicationInUse = true;
communicationService = new CommunicationService(connector, heatPumpConfiguration);
communicationService.setTime();
return true;
} catch (StiebelHeatPumpException e) {
logger.error("Stiebel heatpump time could not be set on heat pump! " + e.toString());
} finally {
communicationInUse = false;
if (communicationService != null) {
communicationService.finalizer();
}
}
return false;
}
/**
* This method publishes all values on the event bus
*
* @param heatPumpData
* as map of provider parameter and value
*/
private void publishValues(Map<String, String> heatPumpData) {
for (StiebelHeatPumpBindingProvider provider : providers) {
publishForProvider(heatPumpData, provider);
}
}
private void publishForProvider(Map<String, String> heatPumpData, StiebelHeatPumpBindingProvider provider) {
for (String itemName : provider.getItemNames()) {
String parameter = provider.getParameter(itemName);
if (parameter != null && heatPumpData.containsKey(parameter)) {
publishItem(itemName, heatPumpData.get(parameter), provider.getItemType(itemName));
}
}
}
private void publishItem(String itemName, String heatpumpValue, Class<? extends Item> itemType) {
if (itemType.isAssignableFrom(NumberItem.class)) {
eventPublisher.postUpdate(itemName, new DecimalType(heatpumpValue));
}
if (itemType.isAssignableFrom(StringItem.class)) {
eventPublisher.postUpdate(itemName, new StringType(heatpumpValue));
}
}
}