/**
* 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.maxcul.internal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import org.openhab.binding.maxcul.MaxCulBindingProvider;
import org.openhab.core.binding.BindingChangeListener;
import org.openhab.core.binding.BindingConfig;
import org.openhab.core.binding.BindingProvider;
import org.openhab.core.items.Item;
import org.openhab.core.library.items.ContactItem;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.model.item.binding.AbstractGenericBindingProvider;
import org.openhab.model.item.binding.BindingConfigParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is responsible for parsing the binding configuration and
* registering the {@link MaxCulBindingProvider}.
*
* The following devices have the following valid types:
* <li>RadiatorThermostat
* - thermostat,temperature,battery,valvepos</li>
* <li>WallThermostat -
* thermostat,temperature,battery</li>
*
* The generic binding configuration format is (optional arguments in []):
* <code>{ maxcul="<deviceType>:<serialNum>:[bindingType]:[configTemp=<comfortTemp>/<ecoTemp>/<maxTemp>/<minTemp>/<windowOpenTemperature>/<windowOpenDuration>/<measurementOffset>]:[assoc=<serialNum>]
*
* Not setting configTemp will use whatever is already programmed into the device. Setting windowOpenTemp to anything other than 'Off' will enable detection of a window being opened using temperature. This would result in the thermostat turning off for windowOpenDuration minutes(?)
* Setting assoc will associate the device specified with the one in the binding. This means that they will communicate directly changes in setpoint etc.
*
* Examples:
* <li><code>{ maxcul="RadiatorThermostat:JEQ1234565" }</code> - will return/set
* the thermostat temperature of radiator thermostat with the serial number
* JEQ0304492</li>
* <li>
* <code>{ maxcul="RadiatorThermostat:JEQ1234565:battery" }</code> - will return
* the battery level of JEQ0304492</li>
* <li>
* <code>{ maxcul="WallThermostat:JEQ1234566:temperature" }</code> - will return
* the temperature of a wall mounted thermostat with serial number JEQ0304447</li>
* <li><code>{ maxcul="WallThermostat:JEQ1234566:thermostat" }</code> - will
* set/return the desired temperature of a wall mounted thermostat with serial
* number JEQ0304447</li>
* <li><code>{ maxcul="PushButton:JEQ1234567" }</code> -
* will default to 'switch' mode</li>
* <li><code>{ maxcul="PairMode" }</code> -
* Switch only, enables pair mode for 60s. Will automatically switch off after
* this time.</li>
* <li><code>{ maxcul="ListenMode" }</code> - Switch only,
* doesn't process messages - just listens to traffic, parses and outputs it.</li>
*
* @author Paul Hampson (cyclingengineer)
* @since 1.6.0
*/
public class MaxCulGenericBindingProvider extends AbstractGenericBindingProvider implements MaxCulBindingProvider {
private boolean associationsMapBuilt = false;
/**
* {@inheritDoc}
*/
@Override
public String getBindingType() {
return "maxcul";
}
private static final Logger logger = LoggerFactory.getLogger(MaxCulGenericBindingProvider.class);
private HashMap<String, HashSet<MaxCulBindingConfig>> associationMap = new HashMap<String, HashSet<MaxCulBindingConfig>>();
/**
* @{inheritDoc
*/
@Override
public void validateItemType(Item item, String bindingConfig) throws BindingConfigParseException {
/* only switch items or number items or contact items are valid */
if (!(item instanceof NumberItem) && !(item instanceof SwitchItem) && !(item instanceof ContactItem)) {
throw new BindingConfigParseException(
"Invalid item type. Bindings can only be Number or Switch or Contact");
}
}
/**
* {@inheritDoc}
*/
@Override
public void processBindingConfiguration(String context, Item item, String bindingConfig)
throws BindingConfigParseException {
super.processBindingConfiguration(context, item, bindingConfig);
final String itemName = item.getName();
logger.debug("Processing item " + itemName);
final MaxCulBindingConfig config = new MaxCulBindingConfig(bindingConfig);
addBindingConfig(item, config);
addBindingChangeListener(new BindingChangeListener() {
@Override
public void bindingChanged(BindingProvider provider, String itemName) {
/* binding changed so update the association map */
associationsMapBuilt = false;
// TODO check if config temperatures are set and flag that they
// should be sent the device because they might have changed?
}
@Override
public void allBindingsChanged(BindingProvider provider) {
if (!provider.providesBindingFor(itemName)) {
// TODO get serial number of itemName
// then check if we still interact with that device, if not
// then
// deassociate and send a reset to it
}
}
});
}
@Override
public MaxCulBindingConfig getConfigForItemName(String itemName) {
MaxCulBindingConfig config = null;
if (super.bindingConfigs.containsKey(itemName)) {
config = (MaxCulBindingConfig) super.bindingConfigs.get(itemName);
}
return config;
}
@Override
public String getItemNameForConfig(MaxCulBindingConfig bc) {
String itemName = null;
if (super.bindingConfigs.containsValue(bc)) {
for (Entry<String, BindingConfig> entry : super.bindingConfigs.entrySet()) {
if (entry.getValue().equals(bc)) {
itemName = entry.getKey();
break;
}
}
}
return itemName;
}
@Override
public MaxCulBindingConfig getConfigForSerialNumber(String serial) {
MaxCulBindingConfig config = null;
for (BindingConfig c : super.bindingConfigs.values()) {
config = (MaxCulBindingConfig) c;
if (config.getSerialNumber().equalsIgnoreCase(serial)) {
return config;
}
}
return null;
}
@Override
public List<MaxCulBindingConfig> getConfigsForSerialNumber(String serial) {
List<MaxCulBindingConfig> configs = new ArrayList<MaxCulBindingConfig>();
for (BindingConfig c : super.bindingConfigs.values()) {
MaxCulBindingConfig config = (MaxCulBindingConfig) c;
if (config.getSerialNumber() != null) /*
* could be PairMode/ListenMode
* device which has no serial
*/
{
if (config.getSerialNumber().compareToIgnoreCase(serial) == 0) {
configs.add(config);
}
}
}
if (configs.isEmpty()) {
return null;
} else {
return configs;
}
}
@Override
public List<MaxCulBindingConfig> getConfigsForRadioAddr(String addr) {
List<MaxCulBindingConfig> configs = new ArrayList<MaxCulBindingConfig>();
for (BindingConfig c : super.bindingConfigs.values()) {
MaxCulBindingConfig config = (MaxCulBindingConfig) c;
if (config.getSerialNumber() != null) /*
* could be PairMode/ListenMode
* device which has no serial
*/
{
if (config.getDevAddr().equalsIgnoreCase(addr)) {
configs.add(config);
}
}
}
return configs;
}
/**
* Loop over all bindings finding their associated devices and create
* entries for each one. So end up with something like (psuedo binding
* code): Binding 1: dev A { assoc=B,C } result: A -> B,C Binding 2: dev B {
* assoc=C } result: A -> B,C B -> C Binding 3: dev B { assoc=D } result: A
* -> B,C B -> C,D
*/
private void buildAssociationMap() {
// TODO refactor into its own class e.g. AssociationMap extends HashMap
if (super.bindingConfigs.values().isEmpty() == false) {
logger.debug(
"Found " + super.bindingConfigs.values().size() + " binding configs to process in association map");
for (BindingConfig c : super.bindingConfigs.values()) {
MaxCulBindingConfig config = (MaxCulBindingConfig) c;
logger.debug("Processing " + config.getSerialNumber() + " with "
+ config.getAssociatedSerialNum().size() + " associations");
if (associationMap.containsKey(config.getSerialNumber())
&& config.getAssociatedSerialNum().isEmpty() == false) {
/*
* serial number already exists in the map so check if we
* need to add any devices to the association
*/
HashSet<MaxCulBindingConfig> set = associationMap.get(config.getSerialNumber());
logger.debug(
"Found " + config.getSerialNumber() + " in map already with " + set.size() + " entrys");
for (String serial : config.getAssociatedSerialNum()) {
MaxCulBindingConfig bc = getConfigForSerialNumber(serial);
if (bc != null) {
boolean deviceInSet = false;
for (MaxCulBindingConfig bcInSet : set) {
if (bcInSet.getSerialNumber().equalsIgnoreCase(bc.getSerialNumber())) {
deviceInSet |= true;
}
}
if (!deviceInSet) {
logger.debug("Adding " + serial + " to set for " + config.getSerialNumber());
set.add(bc);
}
}
}
} else if (config.getAssociatedSerialNum().isEmpty() == false) {
/* new serial number, add it and its associations */
HashSet<MaxCulBindingConfig> set = new HashSet<MaxCulBindingConfig>();
for (String serial : config.getAssociatedSerialNum()) {
/*
* add first config for this serial number. This is
* enough to give us device type and destination address
* which is all we need.
*/
MaxCulBindingConfig bc = getConfigForSerialNumber(serial);
if (bc != null) {
boolean deviceInSet = false;
for (MaxCulBindingConfig bcInSet : set) {
if (bcInSet.getSerialNumber().equalsIgnoreCase(bc.getSerialNumber())) {
deviceInSet |= true;
}
}
if (!deviceInSet) {
logger.debug("Adding " + serial + " to set for " + config.getSerialNumber());
set.add(bc);
}
}
}
/* only add if it has entries */
if (!set.isEmpty()) {
associationMap.put(config.getSerialNumber(), set);
}
}
}
associationsMapBuilt = true;
/* debug print of association map */
if (!associationMap.isEmpty()) {
for (String serialKey : associationMap.keySet()) {
if (serialKey != null) {
logger.debug("Device " + serialKey + " associated with:");
for (MaxCulBindingConfig bc : associationMap.get(serialKey)) {
logger.debug("\t=> " + bc.getSerialNumber());
}
}
}
}
}
}
@Override
public HashSet<MaxCulBindingConfig> getAssociations(String deviceSerial) {
if (associationsMapBuilt == false) {
buildAssociationMap();
}
return associationMap.get(deviceSerial);
}
@Override
public List<MaxCulBindingConfig> getCreditMonitorBindings() {
List<MaxCulBindingConfig> configs = new ArrayList<MaxCulBindingConfig>();
for (BindingConfig c : super.bindingConfigs.values()) {
MaxCulBindingConfig config = (MaxCulBindingConfig) c;
if (config.getDeviceType() == MaxCulDevice.CREDIT_MONITOR) {
configs.add(config);
}
}
return configs;
}
}