/**
* 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.novelanheatpump.internal;
import static org.apache.commons.lang.StringUtils.*;
import java.io.IOException;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Dictionary;
import java.util.Iterator;
import org.openhab.binding.novelanheatpump.HeatPumpBindingProvider;
import org.openhab.binding.novelanheatpump.HeatpumpCommandType;
import org.openhab.binding.novelanheatpump.HeatpumpCoolingOperationMode;
import org.openhab.binding.novelanheatpump.HeatpumpOperationMode;
import org.openhab.binding.novelanheatpump.i18n.Messages;
import org.openhab.binding.novelanheatpump.internal.HeatPumpGenericBindingProvider.HeatPumpBindingConfig;
import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.Command;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The Heatpump binding connects to a Novelan (Siemens) Heatpump with the
* {@link HeatpumpConnector} and read the internal state array every minute.
* With the state array each binding will be updated.
*
* @author Jan-Philipp Bolle
* @author John Cocula -- made port configurable
* @since 1.0.0
*/
public class HeatPumpBinding extends AbstractActiveBinding<HeatPumpBindingProvider>implements ManagedService {
private static final Logger logger = LoggerFactory.getLogger(HeatPumpBinding.class);
private static final SimpleDateFormat sdateformat = new SimpleDateFormat("dd.MM.yy HH:mm"); //$NON-NLS-1$
private static final int DEFAULT_PORT = 8888;
private static final long DEFAULT_REFRESH_INTERVAL = 60000L;
/** Parameter code for heating operation mode */
public static int PARAM_HEATING_OPERATION_MODE = 3;
/** Parameter code for heating temperature */
public static int PARAM_HEATING_TEMPERATURE = 1;
/** Parameter code for warmwater operation mode */
public static int PARAM_WARMWATER_OPERATION_MODE = 4;
/** Parameter code for warmwater temperature */
public static int PARAM_WARMWATER_TEMPERATURE = 2;
/** Parameter code for cooling operation mode */
public static int PARAM_COOLING_OPERATION_MODE = 108;
/** Parameter code for cooling release temperature */
public static int PARAM_COOLING_RELEASE_TEMP = 110;
/** Parameter code for target temp MK1 */
public static int PARAM_COOLING_INLET_TEMP = 132;
/** Parameter code for start cooling after hours */
public static int PARAM_COOLING_START = 850;
/** Parameter code for stop cooling after hours */
public static int PARAM_COOLING_STOP = 851;
/** Default refresh interval (currently 1 minute) */
private long refreshInterval = DEFAULT_REFRESH_INTERVAL;
/* The IP address to connect to */
protected static String ip;
/* the port to connect to. */
protected static int port = DEFAULT_PORT;
@Override
public void deactivate() {
}
@Override
public void activate() {
}
protected void addBindingProvider(HeatPumpBindingProvider bindingProvider) {
super.addBindingProvider(bindingProvider);
}
protected void removeBindingProvider(HeatPumpBindingProvider bindingProvider) {
super.removeBindingProvider(bindingProvider);
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("rawtypes")
public void updated(Dictionary config) throws ConfigurationException {
if (config != null) {
boolean properlyConfigured = true;
long refreshInterval = DEFAULT_REFRESH_INTERVAL;
int port = DEFAULT_PORT;
String ip = null;
try {
String refreshIntervalString = (String) config.get("refresh"); //$NON-NLS-1$
refreshInterval = isNotBlank(refreshIntervalString) ? Long.parseLong(refreshIntervalString)
: DEFAULT_REFRESH_INTERVAL;
ip = (String) config.get("ip"); //$NON-NLS-1$
properlyConfigured = properlyConfigured && isNotBlank(ip);
String portString = (String) config.get("port"); //$NON-NLS-1$
port = isNumeric(portString) ? Integer.parseInt(portString) : DEFAULT_PORT;
} catch (NumberFormatException ex) {
properlyConfigured = false;
}
if (properlyConfigured) {
this.refreshInterval = refreshInterval;
HeatPumpBinding.ip = ip;
HeatPumpBinding.port = port;
}
setProperlyConfigured(properlyConfigured);
}
}
/**
* @{inheritDoc
*/
@Override
public void execute() {
if (!bindingsExist()) {
logger.debug("There is no existing Heatpump binding configuration => refresh cycle aborted!");
return;
}
HeatpumpConnector connector = new HeatpumpConnector(ip, port);
try {
connector.connect();
// read all available values
int[] heatpumpValues = connector.getValues();
// all temperatures are 0.2 degree Celsius exact
// but use int to save values
// example 124 is 12.4 degree Celsius
handleEventType(new DecimalType((double) heatpumpValues[10] / 10),
HeatpumpCommandType.TYPE_TEMPERATURE_SUPPLAY);
handleEventType(new DecimalType((double) heatpumpValues[11] / 10),
HeatpumpCommandType.TYPE_TEMPERATURE_RETURN);
handleEventType(new DecimalType((double) heatpumpValues[12] / 10),
HeatpumpCommandType.TYPE_TEMPERATURE_REFERENCE_RETURN);
handleEventType(new DecimalType((double) heatpumpValues[13] / 10),
HeatpumpCommandType.TYPE_TEMPERATURE_OUT_EXTERNAL);
handleEventType(new DecimalType((double) heatpumpValues[14] / 10),
HeatpumpCommandType.TYPE_TEMPERATURE_HOT_GAS);
handleEventType(new DecimalType((double) heatpumpValues[15] / 10),
HeatpumpCommandType.TYPE_TEMPERATURE_OUTSIDE);
handleEventType(new DecimalType((double) heatpumpValues[16] / 10),
HeatpumpCommandType.TYPE_TEMPERATURE_OUTSIDE_AVG);
handleEventType(new DecimalType((double) heatpumpValues[17] / 10),
HeatpumpCommandType.TYPE_TEMPERATURE_SERVICEWATER);
handleEventType(new DecimalType((double) heatpumpValues[18] / 10),
HeatpumpCommandType.TYPE_TEMPERATURE_SERVICEWATER_REFERENCE);
handleEventType(new DecimalType((double) heatpumpValues[19] / 10),
HeatpumpCommandType.TYPE_TEMPERATURE_PROBE_IN);
handleEventType(new DecimalType((double) heatpumpValues[20] / 10),
HeatpumpCommandType.TYPE_TEMPERATURE_PROBE_OUT);
handleEventType(new DecimalType((double) heatpumpValues[21] / 10),
HeatpumpCommandType.TYPE_TEMPERATURE_MK1);
handleEventType(new DecimalType((double) heatpumpValues[22] / 10),
HeatpumpCommandType.TYPE_TEMPERATURE_MK1_REFERENCE);
handleEventType(new DecimalType((double) heatpumpValues[24] / 10),
HeatpumpCommandType.TYPE_TEMPERATURE_MK2);
handleEventType(new DecimalType((double) heatpumpValues[25] / 10),
HeatpumpCommandType.TYPE_TEMPERATURE_MK2_REFERENCE);
handleEventType(new DecimalType((double) heatpumpValues[26] / 10),
HeatpumpCommandType.TYPE_HEATPUMP_SOLAR_COLLECTOR);
handleEventType(new DecimalType((double) heatpumpValues[27] / 10),
HeatpumpCommandType.TYPE_HEATPUMP_SOLAR_STORAGE);
handleEventType(new DecimalType((double) heatpumpValues[28] / 10),
HeatpumpCommandType.TYPE_TEMPERATURE_EXTERNAL_SOURCE);
handleEventType(new StringType(formatHours(heatpumpValues[56])),
HeatpumpCommandType.TYPE_HOURS_COMPRESSOR1);
handleEventType(new DecimalType((double) heatpumpValues[57]), HeatpumpCommandType.TYPE_STARTS_COMPRESSOR1);
handleEventType(new StringType(formatHours(heatpumpValues[58])),
HeatpumpCommandType.TYPE_HOURS_COMPRESSOR2);
handleEventType(new DecimalType((double) heatpumpValues[59]), HeatpumpCommandType.TYPE_STARTS_COMPRESSOR2);
handleEventType(new StringType(formatHours(heatpumpValues[60])), HeatpumpCommandType.TYPE_HOURS_ZWE1);
handleEventType(new StringType(formatHours(heatpumpValues[61])), HeatpumpCommandType.TYPE_HOURS_ZWE2);
handleEventType(new StringType(formatHours(heatpumpValues[62])), HeatpumpCommandType.TYPE_HOURS_ZWE3);
handleEventType(new StringType(formatHours(heatpumpValues[63])), HeatpumpCommandType.TYPE_HOURS_HETPUMP);
handleEventType(new StringType(formatHours(heatpumpValues[64])), HeatpumpCommandType.TYPE_HOURS_HEATING);
handleEventType(new StringType(formatHours(heatpumpValues[65])), HeatpumpCommandType.TYPE_HOURS_WARMWATER);
handleEventType(new StringType(formatHours(heatpumpValues[66])), HeatpumpCommandType.TYPE_HOURS_COOLING);
handleEventType(new DecimalType((double) heatpumpValues[151] / 10),
HeatpumpCommandType.TYPE_THERMALENERGY_HEATING);
handleEventType(new DecimalType((double) heatpumpValues[152] / 10),
HeatpumpCommandType.TYPE_THERMALENERGY_WARMWATER);
handleEventType(new DecimalType((double) heatpumpValues[153] / 10),
HeatpumpCommandType.TYPE_THERMALENERGY_POOL);
handleEventType(new DecimalType((double) heatpumpValues[154] / 10),
HeatpumpCommandType.TYPE_THERMALENERGY_TOTAL);
handleEventType(new DecimalType((double) heatpumpValues[155]), HeatpumpCommandType.TYPE_MASSFLOW);
String heatpumpState = getStateString(heatpumpValues) + ": " + getStateTime(heatpumpValues); //$NON-NLS-1$
handleEventType(new StringType(heatpumpState), HeatpumpCommandType.TYPE_HEATPUMP_STATE);
String heatpumpExtendedState = getExtendeStateString(heatpumpValues) + ": " //$NON-NLS-1$
+ formatHours(heatpumpValues[120]);
handleEventType(new StringType(heatpumpExtendedState), HeatpumpCommandType.TYPE_HEATPUMP_EXTENDED_STATE);
// read all parameters
int[] heatpumpParams = connector.getParams();
handleEventType(new DecimalType(heatpumpParams[PARAM_HEATING_TEMPERATURE] / 10.),
HeatpumpCommandType.TYPE_HEATING_TEMPERATURE);
handleEventType(new DecimalType(heatpumpParams[PARAM_HEATING_OPERATION_MODE]),
HeatpumpCommandType.TYPE_HEATING_OPERATION_MODE);
handleEventType(new DecimalType(heatpumpParams[PARAM_WARMWATER_TEMPERATURE] / 10.),
HeatpumpCommandType.TYPE_WARMWATER_TEMPERATURE);
handleEventType(new DecimalType(heatpumpParams[PARAM_WARMWATER_OPERATION_MODE]),
HeatpumpCommandType.TYPE_WARMWATER_OPERATION_MODE);
handleEventType(new DecimalType(heatpumpParams[PARAM_COOLING_OPERATION_MODE]),
HeatpumpCommandType.TYPE_COOLING_OPERATION_MODE);
handleEventType(new DecimalType(heatpumpParams[PARAM_COOLING_RELEASE_TEMP] / 10.),
HeatpumpCommandType.TYPE_COOLING_RELEASE_TEMPERATURE);
handleEventType(new DecimalType(heatpumpParams[PARAM_COOLING_INLET_TEMP] / 10.),
HeatpumpCommandType.TYPE_COOLING_INLET_TEMP);
handleEventType(new DecimalType(heatpumpParams[PARAM_COOLING_START] / 10.),
HeatpumpCommandType.TYPE_COOLING_START_AFTER_HOURS);
handleEventType(new DecimalType(heatpumpParams[PARAM_COOLING_STOP] / 10.),
HeatpumpCommandType.TYPE_COOLING_STOP_AFTER_HOURS);
} catch (UnknownHostException e) {
logger.warn("the given hostname '{}' of the Novelan heatpump is unknown", ip);
} catch (IOException e) {
logger.warn("couldn't establish network connection [host '{}']", ip);
} finally {
if (connector != null) {
connector.disconnect();
}
}
}
private void handleEventType(org.openhab.core.types.State state, HeatpumpCommandType heatpumpCommandType) {
for (HeatPumpBindingProvider provider : providers) {
for (String itemName : provider.getItemNamesForType(heatpumpCommandType)) {
eventPublisher.postUpdate(itemName, state);
}
}
}
/**
* generate a readable string containing the time since the heatpump is in
* the state.
*
* @param heatpumpValues
* the internal state array of the heatpump
* @return a human readable time string
*/
private String getStateTime(int[] heatpumpValues) {
String returnValue = ""; //$NON-NLS-1$
// for a long time create a date
if (heatpumpValues[118] == 2) {
long value = heatpumpValues[95];
if (value < 0) {
value = 0;
}
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(value * 1000L);
returnValue += sdateformat.format(cal.getTime());
} else {
// for a shorter time use the counted time (HH:mm:ss)
int value = heatpumpValues[120];
returnValue = formatHours(value);
}
return returnValue;
}
private String formatHours(int value) {
String returnValue = "";
returnValue += String.format("%02d:", new Object[] { Integer.valueOf(value / 3600) }); //$NON-NLS-1$
value %= 3600;
returnValue += String.format("%02d:", new Object[] { Integer.valueOf(value / 60) }); //$NON-NLS-1$
value %= 60;
returnValue += String.format("%02d", new Object[] { Integer.valueOf(value) }); //$NON-NLS-1$
return returnValue;
}
/**
* generate a readable state string from internal heatpump values.
*
* @param heatpumpValues
* the internal state array of the heatpump
* @return a human readable string, the result displays what the heatpump is
* doing
*/
private String getExtendeStateString(int[] heatpumpValues) {
String returnValue = ""; //$NON-NLS-1$
switch (heatpumpValues[119]) {
case -1:
returnValue = Messages.HeatPumpBinding_ERROR;
break;
case 0:
returnValue = Messages.HeatPumpBinding_HEATING;
break;
case 1:
returnValue = Messages.HeatPumpBinding_STANDBY;
break;
case 2:
returnValue = Messages.HeatPumpBinding_SWITCH_ON_DELAY;
break;
case 3:
returnValue = Messages.HeatPumpBinding_SWITCHING_CYCLE_BLOCKING;
break;
case 4:
returnValue = Messages.HeatPumpBinding_PROVIDER_LOCK_TIME;
break;
case 5:
returnValue = Messages.HeatPumpBinding_SERVICE_WATER;
break;
case 6:
returnValue = Messages.HeatPumpBinding_SCREED_HEAT_UP;
break;
case 7:
returnValue = Messages.HeatPumpBinding_DEFROSTING;
break;
case 8:
returnValue = Messages.HeatPumpBinding_PUMP_FLOW;
break;
case 9:
returnValue = Messages.HeatPumpBinding_DISINFECTION;
break;
case 10:
returnValue = Messages.HeatPumpBinding_COOLING;
break;
case 12:
returnValue = Messages.HeatPumpBinding_POOL_WATER;
break;
case 13:
returnValue = Messages.HeatPumpBinding_HEATING_EXT;
break;
case 14:
returnValue = Messages.HeatPumpBinding_SERVICE_WATER_EXT;
break;
case 16:
returnValue = Messages.HeatPumpBinding_FLOW_MONITORING;
break;
case 17:
returnValue = Messages.HeatPumpBinding_ZWE_OPERATION;
break;
case 19:
returnValue = Messages.HeatPumpBinding_SERVICE_WATER_ADDITIONAL_HEATING;
break;
default:
logger.info(
"found new value for reverse engineering !!!! No idea what the heatpump will do in state {}.", //$NON-NLS-1$
heatpumpValues[119]);
returnValue = Messages.HeatPumpBinding_UNKNOWN;
}
return returnValue;
}
/**
* generate a readable state string from internal heatpump values.
*
* @param heatpumpValues
* the internal state array of the heatpump
* @return a human readable string, the result displays what the heatpump is
* doing
*/
private String getStateString(int[] heatpumpValues) {
String returnValue = ""; //$NON-NLS-1$
switch (heatpumpValues[117]) {
case -1:
returnValue = Messages.HeatPumpBinding_ERROR;
break;
case 0:
returnValue = Messages.HeatPumpBinding_RUNNING;
break;
case 1:
returnValue = Messages.HeatPumpBinding_STOPPED;
break;
case 2:
returnValue = Messages.HeatPumpBinding_APPEAR;
break;
case 5:
returnValue = Messages.HeatPumpBinding_DEFROSTING;
break;
default:
logger.info(
"found new value for reverse engineering !!!! No idea what the heatpump will do in state {}.", //$NON-NLS-1$
heatpumpValues[117]);
returnValue = Messages.HeatPumpBinding_UNKNOWN;
}
return returnValue;
}
@Override
protected long getRefreshInterval() {
return refreshInterval;
}
@Override
protected String getName() {
return "Heatpump Refresh Service";
}
@Override
protected void internalReceiveCommand(String itemName, Command command) {
HeatPumpGenericBindingProvider provider = findFirstProvider();
if (provider != null) {
HeatPumpBindingConfig bindingConfig = provider.getHeatPumpBindingConfig(itemName);
HeatpumpCommandType commandType = bindingConfig.getType();
switch (commandType) {
case TYPE_HEATING_OPERATION_MODE:
if (command instanceof DecimalType) {
int value = ((DecimalType) command).intValue();
HeatpumpOperationMode mode = HeatpumpOperationMode.fromValue(value);
if (mode != null) {
if (sendParamToHeatpump(PARAM_HEATING_OPERATION_MODE, mode.getValue())) {
logger.info("Heatpump heating operation mode set to {}.", mode.name());
}
} else {
logger.warn("Heatpump heating operation mode with value {} is unknown.", value);
}
} else {
logger.warn("Heatpump heating operation mode item {} must be from type:{}.", itemName,
DecimalType.class.getSimpleName());
}
break;
case TYPE_HEATING_TEMPERATURE:
if (command instanceof DecimalType) {
float temperature = ((DecimalType) command).floatValue();
int value = (int) (temperature * 10.);
if (sendParamToHeatpump(PARAM_HEATING_TEMPERATURE, value)) {
logger.info("Heatpump heating temperature set to {}.", temperature);
}
} else {
logger.warn("Heatpump heating temperature item {} must be from type:{}.", itemName,
DecimalType.class.getSimpleName());
}
break;
case TYPE_WARMWATER_OPERATION_MODE:
if (command instanceof DecimalType) {
int value = ((DecimalType) command).intValue();
HeatpumpOperationMode mode = HeatpumpOperationMode.fromValue(value);
if (mode != null) {
if (sendParamToHeatpump(PARAM_WARMWATER_OPERATION_MODE, mode.getValue())) {
logger.info("Heatpump warmwater operation mode set to: {}. ", mode.name());
}
} else {
logger.warn("Heatpump warmwater operation mode with value {} is unknown.", value);
}
} else {
logger.warn("Heatpump warmwater operation mode item {} must be from type: {}.", itemName,
DecimalType.class.getSimpleName());
}
break;
case TYPE_WARMWATER_TEMPERATURE:
if (command instanceof DecimalType) {
float temperature = ((DecimalType) command).floatValue();
int value = (int) (temperature * 10.);
if (sendParamToHeatpump(PARAM_WARMWATER_TEMPERATURE, value)) {
logger.info("Heatpump warmwater temperature set to {}.", temperature);
}
} else {
logger.warn("Heatpump warmwater temperature item {} must be from type: {}.", itemName,
DecimalType.class.getSimpleName());
}
break;
case TYPE_COOLING_OPERATION_MODE:
if (command instanceof DecimalType) {
int value = ((DecimalType) command).intValue();
HeatpumpCoolingOperationMode mode = HeatpumpCoolingOperationMode.fromValue(value);
if (mode != null) {
if (sendParamToHeatpump(PARAM_COOLING_OPERATION_MODE, mode.getValue())) {
logger.info("Heatpump cooling operation mode set to {}.", mode.name());
}
} else {
logger.warn("Heatpump cooling operation mode with value {} is unknown.", value);
}
} else {
logger.warn("Heatpump cooling operation mode item {} must be from type: {}.", itemName,
DecimalType.class.getSimpleName());
}
break;
case TYPE_COOLING_RELEASE_TEMPERATURE:
if (command instanceof DecimalType) {
float temperature = ((DecimalType) command).floatValue();
int value = (int) (temperature * 10.);
if (sendParamToHeatpump(PARAM_COOLING_RELEASE_TEMP, value)) {
logger.info("Heatpump cooling release temperature set to {}.", temperature);
}
} else {
logger.warn("Heatpump cooling release temperature item {} must be from type: {}.", itemName,
DecimalType.class.getSimpleName());
}
break;
case TYPE_COOLING_INLET_TEMP:
if (command instanceof DecimalType) {
float temperature = ((DecimalType) command).floatValue();
int value = (int) (temperature * 10.);
if (sendParamToHeatpump(PARAM_COOLING_INLET_TEMP, value)) {
logger.info("Heatpump cooling target temp MK1 set to {}.", temperature);
}
} else {
logger.warn("Heatpump cooling target temp MK1 item {} must be from type: {}.", itemName,
DecimalType.class.getSimpleName());
}
break;
case TYPE_COOLING_START_AFTER_HOURS:
if (command instanceof DecimalType) {
float hours = ((DecimalType) command).floatValue();
int value = (int) (hours * 10.);
if (sendParamToHeatpump(PARAM_COOLING_START, value)) {
logger.info("Heatpump cooling start after hours set to {}.", hours);
}
} else {
logger.warn("Heatpump cooling start after hours item {} must be from type: {}.", itemName,
DecimalType.class.getSimpleName());
}
break;
case TYPE_COOLING_STOP_AFTER_HOURS:
if (command instanceof DecimalType) {
float hours = ((DecimalType) command).floatValue();
int value = (int) (hours * 10.);
if (sendParamToHeatpump(PARAM_COOLING_STOP, value)) {
logger.info("Heatpump cooling stop after hours set to {}.", hours);
}
} else {
logger.warn("Heatpump cooling stop after hours item {} must be from type: {}.", itemName,
DecimalType.class.getSimpleName());
}
break;
default:
}
}
}
/**
* Set a parameter on the Novelan heatpump.
*
* @param param
* @param value
*/
private boolean sendParamToHeatpump(int param, int value) {
HeatpumpConnector connector = new HeatpumpConnector(ip, port);
try {
connector.connect();
return connector.setParam(param, value);
} catch (UnknownHostException e) {
logger.warn("the given hostname '{}' of the Novelan heatpump is unknown", ip);
return false;
} catch (IOException e) {
logger.warn("couldn't establish network connection [host '{}']", ip);
return false;
} finally {
if (connector != null) {
connector.disconnect();
}
}
}
/**
* Finds the binding provider.
*
* @return
*/
private HeatPumpGenericBindingProvider findFirstProvider() {
Iterator<HeatPumpBindingProvider> it = providers.iterator();
while (it.hasNext()) {
HeatPumpBindingProvider provider = it.next();
if (provider instanceof HeatPumpGenericBindingProvider) {
return (HeatPumpGenericBindingProvider) provider;
}
}
return null;
}
}