/**
* 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.zwave.internal.converter;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import org.openhab.binding.zwave.ZWaveBindingConfig;
import org.openhab.binding.zwave.ZWaveBindingProvider;
import org.openhab.binding.zwave.internal.protocol.SerialMessage;
import org.openhab.binding.zwave.internal.protocol.SerialMessage.SerialMessagePriority;
import org.openhab.binding.zwave.internal.protocol.ZWaveController;
import org.openhab.binding.zwave.internal.protocol.ZWaveNode;
import org.openhab.binding.zwave.internal.protocol.ZWaveNodeState;
import org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveCommandClass;
import org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveCommandClass.CommandClass;
import org.openhab.binding.zwave.internal.protocol.event.ZWaveCommandClassValueEvent;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.items.Item;
import org.openhab.core.library.items.ContactItem;
import org.openhab.core.library.items.DimmerItem;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.items.RollershutterItem;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* ZWaveConverterHandler class. Acts as a factory and manager of all the
* converters the binding can use to convert between the Z-Wave api and the
* binding.
*
* @author Jan-Willem Spuij
* @since 1.4.0
*/
public class ZWaveConverterHandler {
private static final Logger logger = LoggerFactory.getLogger(ZWaveConverterHandler.class);
private final Map<CommandClass, ZWaveCommandClassConverter<?>> converters = new HashMap<CommandClass, ZWaveCommandClassConverter<?>>();
private final Map<Class<? extends Item>, CommandClass[]> preferredCommandClasses = new HashMap<Class<? extends Item>, CommandClass[]>();
private final ZWaveController controller;
private final ZWaveInfoConverter infoConverter;
/**
* Constructor. Creates a new instance of the @ link ZWaveConverterHandler}
* class.
*
* @param controller
* the {@link ZWaveController} to use to send messages.
* @param eventPublisher
* the {@link EventPublisher} to use to post updates.
*/
public ZWaveConverterHandler(ZWaveController controller, EventPublisher eventPublisher) {
this.controller = controller;
// add converters here
converters.put(CommandClass.THERMOSTAT_SETPOINT,
new ZWaveThermostatSetpointConverter(controller, eventPublisher));
converters.put(CommandClass.THERMOSTAT_MODE, new ZWaveThermostatModeConverter(controller, eventPublisher));
converters.put(CommandClass.THERMOSTAT_FAN_MODE,
new ZWaveThermostatFanModeConverter(controller, eventPublisher));
converters.put(CommandClass.THERMOSTAT_OPERATING_STATE,
new ZWaveThermostatOperatingStateConverter(controller, eventPublisher));
converters.put(CommandClass.THERMOSTAT_FAN_STATE,
new ZWaveThermostatFanStateConverter(controller, eventPublisher));
converters.put(CommandClass.BATTERY, new ZWaveBatteryConverter(controller, eventPublisher));
converters.put(CommandClass.SWITCH_BINARY, new ZWaveBinarySwitchConverter(controller, eventPublisher));
converters.put(CommandClass.SWITCH_ALL, new ZWaveSwitchAllConverter(controller, eventPublisher));
converters.put(CommandClass.SWITCH_MULTILEVEL, new ZWaveMultiLevelSwitchConverter(controller, eventPublisher));
converters.put(CommandClass.SENSOR_BINARY, new ZWaveBinarySensorConverter(controller, eventPublisher));
converters.put(CommandClass.SENSOR_MULTILEVEL, new ZWaveMultiLevelSensorConverter(controller, eventPublisher));
converters.put(CommandClass.SENSOR_ALARM, new ZWaveAlarmSensorConverter(controller, eventPublisher));
converters.put(CommandClass.METER, new ZWaveMeterConverter(controller, eventPublisher));
converters.put(CommandClass.BASIC, new ZWaveBasicConverter(controller, eventPublisher));
converters.put(CommandClass.SCENE_ACTIVATION, new ZWaveSceneConverter(controller, eventPublisher));
converters.put(CommandClass.CENTRAL_SCENE, new ZWaveCentralSceneConverter(controller, eventPublisher));
converters.put(CommandClass.FIBARO_FGRM_222, new FibaroFGRM222Converter(controller, eventPublisher));
converters.put(CommandClass.ALARM, new ZWaveAlarmConverter(controller, eventPublisher));
converters.put(CommandClass.CONFIGURATION, new ZWaveConfigurationConverter(controller, eventPublisher));
converters.put(CommandClass.INDICATOR, new ZWaveIndicatorConverter(controller, eventPublisher));
converters.put(CommandClass.WAKE_UP, new ZWaveWakeUpConverter(controller, eventPublisher));
converters.put(CommandClass.DOOR_LOCK, new ZWaveDoorLockConverter(controller, eventPublisher));
converters.put(CommandClass.BARRIER_OPERATOR, new ZWaveBarrierOperatorConverter(controller, eventPublisher));
converters.put(CommandClass.CLOCK, new ZWaveClockConverter(controller, eventPublisher));
infoConverter = new ZWaveInfoConverter(controller, eventPublisher);
// add preferred command classes per Item class here
preferredCommandClasses
.put(SwitchItem.class,
new CommandClass[] { CommandClass.SWITCH_BINARY, CommandClass.SWITCH_MULTILEVEL,
CommandClass.METER, CommandClass.BASIC, CommandClass.SENSOR_BINARY,
CommandClass.SENSOR_ALARM, CommandClass.DOOR_LOCK });
preferredCommandClasses
.put(DimmerItem.class,
new CommandClass[] { CommandClass.SWITCH_MULTILEVEL, CommandClass.SWITCH_BINARY,
CommandClass.BASIC, CommandClass.SENSOR_MULTILEVEL, CommandClass.SENSOR_BINARY,
CommandClass.SENSOR_ALARM });
preferredCommandClasses
.put(RollershutterItem.class,
new CommandClass[] { CommandClass.SWITCH_MULTILEVEL, CommandClass.SWITCH_BINARY,
CommandClass.BASIC, CommandClass.SENSOR_MULTILEVEL, CommandClass.SENSOR_BINARY,
CommandClass.SENSOR_ALARM });
preferredCommandClasses.put(NumberItem.class,
new CommandClass[] { CommandClass.SENSOR_MULTILEVEL, CommandClass.METER, CommandClass.SWITCH_MULTILEVEL,
CommandClass.BATTERY, CommandClass.BASIC, CommandClass.SENSOR_BINARY, CommandClass.SENSOR_ALARM,
CommandClass.SWITCH_BINARY, CommandClass.THERMOSTAT_SETPOINT, CommandClass.THERMOSTAT_MODE,
CommandClass.THERMOSTAT_FAN_MODE, CommandClass.THERMOSTAT_OPERATING_STATE,
CommandClass.THERMOSTAT_FAN_STATE, CommandClass.WAKE_UP });
preferredCommandClasses.put(ContactItem.class, new CommandClass[] { CommandClass.SENSOR_BINARY,
CommandClass.SENSOR_ALARM, CommandClass.SWITCH_BINARY, CommandClass.BASIC });
}
/**
* Returns a converter to convert between the Z-Wave API and the binding.
*
* @param commandClass
* the {@link CommandClass} to create a converter for.
* @return a {@link ZWaveCommandClassConverter} or null if a converter is
* not found.
*/
public ZWaveCommandClassConverter<?> getConverter(CommandClass commandClass) {
return converters.get(commandClass);
}
/**
* Returns the command class that provides the best suitable converter to
* convert between the Z-Wave API and the binding.
*
* @param item
* the {@link item} to resolve a converter for.
* @param node
* the {@link ZWaveNode} node to resolve a Command class on.
* @param the
* enpoint ID to use to resolve a converter.
* @return the {@link ZWaveCommandClass} that can be used to get a converter
* suitable to do the conversion.
*/
private ZWaveCommandClass resolveConverter(Item item, ZWaveNode node, int endpointId) {
if (item == null) {
return null;
}
if (!preferredCommandClasses.containsKey(item.getClass())) {
logger.warn("No preferred command classes found for item class = {}", item.getClass().toString());
return null;
}
for (CommandClass commandClass : preferredCommandClasses.get(item.getClass())) {
ZWaveCommandClass result = node.resolveCommandClass(commandClass, endpointId);
if (result != null && converters.containsKey(commandClass)) {
return result;
}
}
logger.warn("No matching command classes found for item class = {}, node id = {}, endpoint id = {}",
item.getClass().toString(), node.getNodeId(), endpointId);
return null;
}
/**
* Execute refresh method. This method is called every time a binding item
* is refreshed and the corresponding node should be sent a message.
*
* @param provider
* the {@link ZWaveBindingProvider} that provides the item
* @param itemName
* the name of the item to poll.
* @param forceRefresh
* indicates that a polling refresh should be forced.
*/
@SuppressWarnings("unchecked")
public void executeRefresh(ZWaveBindingProvider provider, String itemName, boolean forceRefresh) {
ZWaveBindingConfig bindingConfiguration = provider.getZwaveBindingConfig(itemName);
ZWaveCommandClass commandClass;
String commandClassName = bindingConfiguration.getArguments().get("command");
// this binding is configured not to poll.
if (!forceRefresh && bindingConfiguration.getRefreshInterval() != null
&& 0 == bindingConfiguration.getRefreshInterval()) {
return;
}
ZWaveNode node = this.controller.getNode(bindingConfiguration.getNodeId());
// ignore nodes that are not initialized.
if (node == null) {
return;
}
if (commandClassName != null) {
// this is a report item, handle it with the report info converter.
if (commandClassName.equalsIgnoreCase("info")) {
infoConverter.executeRefresh(provider.getItem(itemName), node, bindingConfiguration.getEndpoint(),
bindingConfiguration.getArguments());
return;
}
// ignore nodes that are not initialized or dead.
if (node.getNodeState() != ZWaveNodeState.ALIVE || node.isInitializationComplete() == false) {
return;
}
commandClass = node.resolveCommandClass(CommandClass.getCommandClass(commandClassName),
bindingConfiguration.getEndpoint());
if (commandClass == null) {
logger.warn("No command class found for item = {}, command class name = {}, ignoring execute refresh.",
itemName, commandClassName);
return;
}
} else {
commandClass = resolveConverter(provider.getItem(itemName), node, bindingConfiguration.getEndpoint());
}
if (commandClass == null) {
logger.warn("No converter found for item = {}, ignoring execute refresh.", itemName);
return;
}
ZWaveCommandClassConverter<ZWaveCommandClass> converter = (ZWaveCommandClassConverter<ZWaveCommandClass>) getConverter(
commandClass.getCommandClass());
if (converter == null) {
logger.warn("No converter found for item = {}, ignoring execute refresh.", itemName);
return;
}
if (bindingConfiguration.getRefreshInterval() == null) {
bindingConfiguration.setRefreshInterval(converter.getRefreshInterval());
// this binding is configured not to poll.
if (!forceRefresh && 0 == bindingConfiguration.getRefreshInterval()) {
return;
}
}
// not enough time has passed to refresh the item.
if (!forceRefresh && bindingConfiguration.getLastRefreshed() != null
&& (bindingConfiguration.getLastRefreshed().getTime()
+ (bindingConfiguration.getRefreshInterval() * 1000) > Calendar.getInstance()
.getTimeInMillis())) {
return;
}
bindingConfiguration.setLastRefreshed(Calendar.getInstance().getTime());
SerialMessage serialMessage = converter.executeRefresh(node, commandClass, bindingConfiguration.getEndpoint(),
bindingConfiguration.getArguments());
if (serialMessage == null) {
logger.warn("NODE {}: Generating message failed for command class = {}", node.getNodeId(),
commandClass.getCommandClass().getLabel());
return;
}
// This is a poll - treat it as a low priority!
serialMessage.setPriority(SerialMessagePriority.Poll);
// Queue the message
this.controller.sendData(serialMessage);
}
/**
* Get the refresh interval for an item binding
*
* @param provider
* the {@link ZWaveBindingProvider} that provides the item
* @param itemName
* the name of the item to poll.
*/
@SuppressWarnings("unchecked")
public Integer getRefreshInterval(ZWaveBindingProvider provider, String itemName) {
ZWaveBindingConfig bindingConfiguration = provider.getZwaveBindingConfig(itemName);
ZWaveCommandClass commandClass;
String commandClassName = bindingConfiguration.getArguments().get("command");
// this binding is configured not to poll.
if (bindingConfiguration.getRefreshInterval() != null && 0 == bindingConfiguration.getRefreshInterval()) {
return 0;
}
ZWaveNode node = this.controller.getNode(bindingConfiguration.getNodeId());
// ignore nodes that are not initialized.
if (node == null) {
return 0;
}
if (commandClassName != null) {
// this is a report item, handle it with the report info converter.
if (commandClassName.equalsIgnoreCase("info")) {
return infoConverter.getRefreshInterval();
}
if (node.getNodeId() == this.controller.getOwnNodeId() && commandClassName.equalsIgnoreCase("switch_all")) {
return 0;
}
commandClass = node.resolveCommandClass(CommandClass.getCommandClass(commandClassName),
bindingConfiguration.getEndpoint());
if (commandClass == null) {
logger.warn("No command class found for item = {}, command class name = {}, using 0 refresh interval.",
itemName, commandClassName);
return 0;
}
} else {
commandClass = resolveConverter(provider.getItem(itemName), node, bindingConfiguration.getEndpoint());
}
if (commandClass == null) {
logger.warn("No converter found for item = {}, using 0 refresh interval.", itemName);
return 0;
}
ZWaveCommandClassConverter<ZWaveCommandClass> converter = (ZWaveCommandClassConverter<ZWaveCommandClass>) getConverter(
commandClass.getCommandClass());
if (converter == null) {
logger.warn("No converter found for item = {}, using 0 refresh interval.", itemName);
return 0;
}
if (bindingConfiguration.getRefreshInterval() == null) {
bindingConfiguration.setRefreshInterval(converter.getRefreshInterval());
}
return bindingConfiguration.getRefreshInterval();
}
/**
* Handles an incoming {@link ZWaveCommandClassValueEvent}. Implement this
* message in derived classes to convert the value and post an update on the
* openHAB bus.
*
* @param provider
* the {@link ZWaveBindingProvider} that provides the item
* @param itemName
* the name of the item that will receive the event.
* @param event
* the received {@link ZWaveCommandClassValueEvent}.
*/
public void handleEvent(ZWaveBindingProvider provider, String itemName, ZWaveCommandClassValueEvent event) {
ZWaveBindingConfig bindingConfiguration = provider.getZwaveBindingConfig(itemName);
Item item = provider.getItem(itemName);
String commandClassName = bindingConfiguration.getArguments().get("command");
boolean respondToBasic = "true".equalsIgnoreCase(bindingConfiguration.getArguments().get("respond_to_basic"));
logger.trace("Getting converter for item = {}, command class = {}, item command class = {}", itemName,
event.getCommandClass().getLabel(), commandClassName);
if (item == null) {
return;
}
// check whether this item is bound to the right command class.
if (commandClassName != null
&& !commandClassName.equalsIgnoreCase(event.getCommandClass().getLabel().toLowerCase())
&& !(respondToBasic && event.getCommandClass() == CommandClass.BASIC)) {
return;
}
ZWaveCommandClassConverter<?> converter = this.getConverter(event.getCommandClass());
if (converter == null) {
logger.warn("No converter found for command class = {}, ignoring event.",
event.getCommandClass().toString());
return;
}
converter.handleEvent(event, item, bindingConfiguration.getArguments());
}
/**
* Receives a command from openHAB and translates it to an operation on the
* Z-Wave network.
*
* @param provider
* the {@link ZWaveBindingProvider} that provides the item
* @param itemName
* the name of the item that will receive the event.
* @param command
* the received {@link Command}
*/
@SuppressWarnings("unchecked")
public void receiveCommand(ZWaveBindingProvider provider, String itemName, Command command) {
ZWaveBindingConfig bindingConfiguration = provider.getZwaveBindingConfig(itemName);
ZWaveNode node = this.controller.getNode(bindingConfiguration.getNodeId());
if (node == null) {
logger.error("Item {} has non existant node {}", itemName, bindingConfiguration.getNodeId());
return;
}
ZWaveCommandClass commandClass;
String commandClassName = bindingConfiguration.getArguments().get("command");
if (commandClassName != null) {
if (node.getNodeId() == this.controller.getOwnNodeId() && commandClassName.equalsIgnoreCase("switch_all")) {
commandClass = ZWaveCommandClass.getInstance(0x27, node, this.controller);
} else {
commandClass = node.resolveCommandClass(CommandClass.getCommandClass(commandClassName),
bindingConfiguration.getEndpoint());
if (commandClass == null) {
logger.warn(
"NODE {}: No command class found for item = {}. Class = {}({}), endpoint = {}. Ignoring command.",
node.getNodeId(), itemName, commandClassName,
CommandClass.getCommandClass(commandClassName).toString(),
bindingConfiguration.getEndpoint());
return;
}
}
} else {
commandClass = resolveConverter(provider.getItem(itemName), node, bindingConfiguration.getEndpoint());
}
if (commandClass == null) {
logger.warn("NODE {}: No converter found for item = {}, ignoring command.", node.getNodeId(), itemName);
return;
}
ZWaveCommandClassConverter<ZWaveCommandClass> converter = (ZWaveCommandClassConverter<ZWaveCommandClass>) getConverter(
commandClass.getCommandClass());
if (converter == null) {
logger.warn("NODE {}: No converter found for item = {}, ignoring command.", node.getNodeId(), itemName);
return;
}
converter.receiveCommand(provider.getItem(itemName), command, node, commandClass,
bindingConfiguration.getEndpoint(), bindingConfiguration.getArguments());
}
}