/**
* 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.heatmiser.internal;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.EventObject;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.heatmiser.HeatmiserBindingProvider;
import org.openhab.binding.heatmiser.internal.thermostat.HeatmiserPRT;
import org.openhab.binding.heatmiser.internal.thermostat.HeatmiserPRTHW;
import org.openhab.binding.heatmiser.internal.thermostat.HeatmiserThermostat;
import org.openhab.binding.heatmiser.internal.thermostat.HeatmiserThermostat.Functions;
import org.openhab.core.binding.AbstractActiveBinding;
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;
/**
* This class implements the Heatmiser binding. It actively polls all thermostats and sets the item values.
*
* The pollingTable is created from the item bindings, and a separate thermostat array is maintained from
* the responses. The two are separated to allow the system to determine the thermostat type based on the response
* rather than requiring this additional information in the binding string.
*
* The pollingTable is recreated after each complete poll cycle to allow for new bindings
*
* @author Chris Jackson
* @since 1.4.0
*/
public class HeatmiserBinding extends AbstractActiveBinding<HeatmiserBindingProvider>implements ManagedService {
private static final Logger logger = LoggerFactory.getLogger(HeatmiserBinding.class);
private String ipAddress;
private int ipPort;
private final int DefaultPort = 1024;
// Polling and receiving are separated so that we can automatically detect the type of thermostat via the receive
// packet.
private Iterator<Integer> pollIterator = null;
private List<Integer> pollingTable = new ArrayList<Integer>();
private List<HeatmiserThermostat> thermostatTable = new ArrayList<HeatmiserThermostat>();
private MessageListener eventListener = new MessageListener();
private HeatmiserConnector connector = null;
/**
* the refresh interval which is used to poll values from the Heatmiser
* system (optional, defaults to 2000ms)
*/
private long refreshInterval = 2000;
public HeatmiserBinding() {
}
@Override
public void activate() {
logger.debug("Heatmiser binding activated");
super.activate();
}
@Override
public void deactivate() {
logger.debug("Heatmiser binding deactivated");
stopListening();
}
private void listen() {
stopListening();
connector = new HeatmiserConnector();
if (connector != null) {
// Initialise the IP connection
connector.addEventListener(eventListener);
try {
connector.connect(ipAddress, ipPort);
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void stopListening() {
if (connector != null) {
connector.disconnect();
connector.removeEventListener(eventListener);
connector = null;
}
}
/**
* @{inheritDoc}
*/
@Override
protected long getRefreshInterval() {
return refreshInterval;
}
/**
* @{inheritDoc}
*/
@Override
protected String getName() {
return "Heatmiser Refresh Service";
}
/**
* @{inheritDoc}
*/
@Override
protected void execute() {
// the frequently executed code (polling) goes here ...
logger.debug("HEATMISER execute() method is called!");
if (pollingTable == null) {
return;
}
if (pollIterator == null) {
// Rebuild the polling table
pollingTable = new ArrayList<Integer>();
if (pollingTable == null) {
logger.error("HEATMISER error creating pollingTable");
return;
}
// Detect all thermostats from the items and add them to the polling table
for (int address = 0; address < 16; address++) {
for (HeatmiserBindingProvider provider : providers) {
if (provider.getBindingItemsAtAddress(address).size() != 0) {
pollingTable.add(address);
}
}
}
pollIterator = pollingTable.iterator();
}
if (pollIterator.hasNext() == false) {
pollIterator = null;
return;
}
int pollAddress = pollIterator.next();
HeatmiserThermostat pollThermostat = new HeatmiserThermostat();
logger.debug("HEATMISER: polling {}", pollAddress);
pollThermostat.setAddress((byte) pollAddress);
if (pollIterator.hasNext() == false) {
pollIterator = null;
}
connector.sendMessage(pollThermostat.pollThermostat());
}
/**
* @{inheritDoc}
*/
@Override
protected void internalReceiveCommand(String itemName, Command command) {
logger.debug("Heatmiser Command: {} to {}", itemName, command);
HeatmiserBindingProvider providerCmd = null;
for (HeatmiserBindingProvider provider : this.providers) {
int address = provider.getAddress(itemName);
if (address != -1) {
providerCmd = provider;
break;
}
}
if (providerCmd == null) {
logger.debug("Heatmiser command provider not found!!");
return;
}
logger.debug("Heatmiser command provider is: {}", providerCmd);
int address = providerCmd.getAddress(itemName);
Functions function = providerCmd.getFunction(itemName);
for (HeatmiserThermostat thermostat : thermostatTable) {
if (thermostat.getAddress() == address) {
logger.debug("Heatmiser command found thermostat: {}", thermostat);
// Found the thermostat
byte[] commandPacket = thermostat.formatCommand(function, command);
if (commandPacket == null) {
logger.debug("Heatmiser command packet null");
} else {
connector.sendMessage(commandPacket);
}
return;
}
}
}
protected void addBindingProvider(HeatmiserBindingProvider bindingProvider) {
super.addBindingProvider(bindingProvider);
}
protected void removeBindingProvider(HeatmiserBindingProvider bindingProvider) {
super.removeBindingProvider(bindingProvider);
}
/**
* {@inheritDoc}
*/
@Override
public void updated(Dictionary<String, ?> config) throws ConfigurationException {
logger.debug("HEATMISER updated() method is called!");
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);
}
String localAddress = (String) config.get("address");
if (StringUtils.isNotBlank(localAddress)) {
ipAddress = localAddress;
}
String portConfig = (String) config.get("port");
if (StringUtils.isNotBlank(portConfig)) {
ipPort = Integer.parseInt(portConfig);
} else {
ipPort = DefaultPort;
}
// start the listener
listen();
// Tell the system we're good!
setProperlyConfigured(true);
}
}
/**
* Receives incoming packets
*/
private class MessageListener implements HeatmiserEventListener {
HeatmiserThermostat thermostatPacket = null;
@Override
public void packetReceived(EventObject event, byte[] packet) {
thermostatPacket = new HeatmiserThermostat();
if (thermostatPacket.setData(packet) == false) {
return;
}
for (HeatmiserThermostat thermostat : thermostatTable) {
if (thermostat.getAddress() == thermostatPacket.getAddress()) {
// Found the thermostat
thermostat.setData(packet);
processItems(thermostat);
return;
}
}
// Thermostat not found in the list of known devices
// Create a new thermostat and add it to the array
HeatmiserThermostat newThermostat = null;
switch (thermostatPacket.getModel()) {
case PRT:
case PRTE:
newThermostat = new HeatmiserPRT();
break;
case PRTHW:
newThermostat = new HeatmiserPRTHW();
break;
default:
logger.error("Unknown heatmiser thermostat type {} at address {}", thermostatPacket.getModel(),
thermostatPacket.getAddress());
break;
}
// Add the new thermostat to the list
if (newThermostat != null) {
newThermostat.setData(packet);
thermostatTable.add(newThermostat);
processItems(newThermostat);
}
}
private void processItems(HeatmiserThermostat thermostat) {
for (HeatmiserBindingProvider provider : providers) {
for (String itemName : provider.getBindingItemsAtAddress(thermostat.getAddress())) {
State state = null;
switch (provider.getFunction(itemName)) {
case FROSTTEMP:
state = thermostat.getFrostTemperature(provider.getItemType(itemName));
break;
case FLOORTEMP:
state = thermostat.getFloorTemperature(provider.getItemType(itemName));
break;
case ONOFF:
state = thermostat.getOnOffState(provider.getItemType(itemName));
break;
case HEATSTATE:
state = thermostat.getHeatState(provider.getItemType(itemName));
break;
case ROOMTEMP:
state = thermostat.getTemperature(provider.getItemType(itemName));
break;
case SETTEMP:
state = thermostat.getSetTemperature(provider.getItemType(itemName));
break;
case WATERSTATE:
state = thermostat.getWaterState(provider.getItemType(itemName));
break;
case HOLIDAYTIME:
state = thermostat.getHolidayTime(provider.getItemType(itemName));
break;
case HOLIDAYSET:
state = thermostat.getHolidaySet(provider.getItemType(itemName));
break;
case HOLIDAYMODE:
state = thermostat.getHolidayMode(provider.getItemType(itemName));
break;
case HOLDTIME:
state = thermostat.getHoldTime(provider.getItemType(itemName));
break;
case HOLDMODE:
state = thermostat.getHoldTime(provider.getItemType(itemName));
break;
case STATE:
state = thermostat.getState(provider.getItemType(itemName));
break;
default:
break;
}
if (state != null) {
eventPublisher.postUpdate(itemName, state);
} else {
logger.error("'{}' couldn't be parsed to a State.", itemName);
}
}
}
}
}
}