/**
* 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.HashMap;
import java.util.Map;
import org.openhab.binding.zwave.internal.converter.command.BinaryOnOffCommandConverter;
import org.openhab.binding.zwave.internal.converter.command.IntegerCommandConverter;
import org.openhab.binding.zwave.internal.converter.command.ZWaveCommandConverter;
import org.openhab.binding.zwave.internal.converter.state.IntegerDecimalTypeConverter;
import org.openhab.binding.zwave.internal.converter.state.IntegerOnOffTypeConverter;
import org.openhab.binding.zwave.internal.converter.state.IntegerOpenClosedTypeConverter;
import org.openhab.binding.zwave.internal.converter.state.IntegerPercentTypeConverter;
import org.openhab.binding.zwave.internal.converter.state.ZWaveStateConverter;
import org.openhab.binding.zwave.internal.protocol.SerialMessage;
import org.openhab.binding.zwave.internal.protocol.ZWaveController;
import org.openhab.binding.zwave.internal.protocol.ZWaveNode;
import org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveCommandClass.CommandClass;
import org.openhab.binding.zwave.internal.protocol.commandclass.ZWaveIndicatorCommandClass;
import org.openhab.binding.zwave.internal.protocol.event.ZWaveCommandClassValueEvent;
import org.openhab.binding.zwave.internal.protocol.event.ZWaveIndicatorCommandClassChangeEvent;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.items.Item;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/***
* ZWaveIndicatorConverter class. Converter for communication with the
* {@link ZWaveIndicatorCommandClass}.
*
* @author Pedro Paixao
* @since 1.8.0
*/
public class ZWaveIndicatorConverter extends ZWaveCommandClassConverter<ZWaveIndicatorCommandClass> {
private static final Logger logger = LoggerFactory.getLogger(ZWaveIndicatorConverter.class);
private static final int REFRESH_INTERVAL = 0; // refresh interval in seconds for the binary switch;
private static final int ON = 1; // A bit is set to 1 (ON)
private static final int OFF = 0; // A bit is set to 0 (OFF)
private static final int PENDING_ON = 1; // Pending ON change
private static final int PENDING_OFF = -1; // Pending OFF Change
private static final int NO_PENDING_CHANGES = 0; // No pending changes for a bit
private HashMap<String, HashMap<Integer, Integer>> pending;
/**
* Constructor. Creates a new instance of the {@link ZWaveIndicatorConverter} class.
*
* @param controller the {@link ZWaveController} to use for sending messages.
* @param eventPublisher the {@link EventPublisher} to use to publish events.
*/
public ZWaveIndicatorConverter(ZWaveController controller, EventPublisher eventPublisher) {
super(controller, eventPublisher);
// State and command converters used by this converter.
this.addStateConverter(new IntegerDecimalTypeConverter());
this.addStateConverter(new IntegerPercentTypeConverter());
this.addStateConverter(new IntegerOnOffTypeConverter());
this.addStateConverter(new IntegerOpenClosedTypeConverter());
this.addCommandConverter(new IntegerCommandConverter());
this.addCommandConverter(new BinaryOnOffCommandConverter());
pending = new HashMap<String, HashMap<Integer, Integer>>();
}
/**
* {@inheritDoc}
*/
@Override
public SerialMessage executeRefresh(ZWaveNode node, ZWaveIndicatorCommandClass commandClass, int endpointId,
Map<String, String> arguments) {
logger.debug("NODE {}: Generating poll message for {}, endpoint {}", node.getNodeId(),
commandClass.getCommandClass().getLabel(), endpointId);
return node.encapsulate(commandClass.getValueMessage(), commandClass, endpointId);
}
/**
* {@inheritDoc}
*
* Process Z-Wave Indicator Report events. Apply any pending changes (see receiveCommand) and if
* indicator is changed in any way issue an INDICATOR_SET message to update the node's indicator value
*/
@Override
public void handleEvent(ZWaveCommandClassValueEvent event, Item item, Map<String, String> arguments) {
ZWaveStateConverter<?, ?> converter = this.getStateConverter(item, event.getValue());
if (converter == null) {
logger.warn("NODE {}: No converter found for item = {}, ignoring event.", event.getNodeId(),
item.getName());
return;
}
if (!arguments.containsKey("bit")) {
logger.error("NODE {}: You must specify an Indicator bit for item = {}, ignoring event.", event.getNodeId(),
item.getName());
return;
}
int bit = Integer.parseInt(arguments.get("bit"));
ZWaveIndicatorCommandClassChangeEvent e = (ZWaveIndicatorCommandClassChangeEvent) event;
// Get INDICATOR Command Class to check if node supports it and later to generate the INDICATOR_SET message
// to the node to set the new indicator value
ZWaveNode node = this.getController().getNode(event.getNodeId());
ZWaveIndicatorCommandClass commandClass = (ZWaveIndicatorCommandClass) node
.getCommandClass(CommandClass.INDICATOR);
if (commandClass == null) {
logger.warn("NODE {}: Indicator command Class not supported for Item = {}, ignoring event.",
event.getNodeId(), item.getName());
return;
}
// Assume no changes and get the actual indicator value just received from the node
boolean madeChanges = false;
int indicator = (Integer) e.getValue();
logger.warn(String.format("NODE %d: Processing Indicator Event for Item %s indicator = 0x%02X, bit = %d.",
event.getNodeId(), item.getName(), indicator, bit));
// Check if the bound bit is ON (1) or OFF (0)
int isOn = (((byte) indicator) >> bit) & 0x01;
// Check for pending changes from previous receiveCommand() method calls
if (pending.containsKey(item.getName())) {
if (pending.get(item.getName()).containsKey(bit)) {
logger.warn(String.format("NODE %d: There are pending changes to Item %s bit = %d", event.getNodeId(),
item.getName(), bit));
// check if there are any pending set to ON for the bound bit, and if the current bit value is OFF
if (pending.get(item.getName()).get(bit) == PENDING_ON && isOn == OFF) {
// Set bit to ON in current indicator value
byte buttonMask = (byte) (0x01 << bit);
indicator = (byte) (indicator | buttonMask);
HashMap<Integer, Integer> h = new HashMap<Integer, Integer>();
h.put(bit, NO_PENDING_CHANGES);
pending.put(item.getName(), h);
madeChanges = true;
logger.warn(String.format("NODE %d: Item %s indicator = 0x%02X, Set bit = %d to ON - DONE",
event.getNodeId(), item.getName(), indicator, bit));
}
if (pending.get(item.getName()).get(bit) == PENDING_OFF && isOn == ON) {
// Set bit to OFF in current indicator value
byte buttonMask = (byte) ~(0x01 << bit);
indicator = (byte) (indicator & buttonMask);
HashMap<Integer, Integer> h = new HashMap<Integer, Integer>();
h.put(bit, NO_PENDING_CHANGES);
pending.put(item.getName(), h);
madeChanges = true;
logger.warn(String.format("NODE %d: Item %s indicator = 0x%02X, Set bit = %d to OFF - DONE",
event.getNodeId(), item.getName(), indicator, bit));
}
// Check if changes were made send an INDICATOR_SET message to update the node's indicator value
if (madeChanges) {
// recalculate because we made changes to Indicator
isOn = (((byte) indicator) >> bit) & 0x01;
// Create an Indicator SET message and send it to the node
SerialMessage serialMessage = node.encapsulate(commandClass.setValueMessage(indicator),
commandClass, 0);
if (serialMessage == null) {
logger.warn("NODE {}: Generating message failed for command class = {}, endpoint = {}",
node.getNodeId(), commandClass.getCommandClass().getLabel());
return;
}
this.getController().sendData(serialMessage);
logger.warn(String.format("NODE %d: Item %s indicator = 0x%02X, Send pending operations to node",
event.getNodeId(), item.getName(), indicator, bit));
}
} else {
logger.warn(String.format("NODE %d: No pending indicator changes to Item %s bit = %d",
event.getNodeId(), item.getName(), bit));
}
} else {
logger.warn(String.format("NODE %d: No pending Indicator changes to Item %s bit = %d", event.getNodeId(),
item.getName(), bit));
}
if (isOn == ON) {
this.getEventPublisher().postUpdate(item.getName(), converter.convertFromValueToState(0xFF));
} else {
this.getEventPublisher().postUpdate(item.getName(), converter.convertFromValueToState(0x00));
}
}
/**
* {@inheritDoc}
*
* The node's indicator status can change at any time, and without the knowledge of the Z-Wave binding so
* before any changes are made to the indicator we need to get its actual value from the device.
* Hence the receiveCommand method adds the desired command to a "pending" list, and issues an INDICATOR_GET
* it does not change the actual device Indicator value.
*
* When the node responds with the INDICATOR_REPORT pending commands are applied to the indicator value
* and the result is then sent to the node. This is performed in the handleEvent() method.
*
* Pending changes are stored in a hash of hashes with the following keys "ItemName" and "bit" value is 1
* for turn bit ON, -1 to turn the bound bit OFF, 0 no change.
*
* HashMap<ItemName, HashMap<bit, pendingOperationCode>>
*/
@Override
public void receiveCommand(Item item, Command command, ZWaveNode node, ZWaveIndicatorCommandClass commandClass,
int endpointId, Map<String, String> arguments) {
ZWaveCommandConverter<?, ?> converter = this.getCommandConverter(command.getClass());
if (converter == null) {
logger.warn("NODE {}: No converter found for item = {}, endpoint = {}, ignoring command.", node.getNodeId(),
item.getName(), endpointId);
return;
}
if (!arguments.containsKey("bit")) {
logger.error("NODE {}: You must specify an Indicator bit for item = {}, endpoint = {}, ignoring command.",
node.getNodeId(), item.getName(), endpointId);
return;
}
int bit = Integer.parseInt(arguments.get("bit"));
if (!(command instanceof OnOffType)) {
return;
}
if (command == OnOffType.ON) {
HashMap<Integer, Integer> h = new HashMap<Integer, Integer>();
h.put(bit, PENDING_ON);
pending.put(item.getName(), h);
logger.warn(String.format("NODE %d: Item %s Set bit = %d to ON - PENDING", node.getNodeId(), item.getName(),
bit));
} else {
logger.warn(String.format("NODE %d: Item %s Set bit = %d to OFF - PENDING", node.getNodeId(),
item.getName(), bit));
HashMap<Integer, Integer> h = new HashMap<Integer, Integer>();
h.put(bit, PENDING_OFF);
pending.put(item.getName(), h);
}
// Issue a INDICATOR_GET message to the node to get the updated indicator value
SerialMessage serialMessage = node.encapsulate(commandClass.getValueMessage(), commandClass, endpointId);
if (serialMessage == null) {
logger.warn("NODE {}: Generating message failed for command class = {}, endpoint = {}", node.getNodeId(),
commandClass.getCommandClass().getLabel(), endpointId);
return;
}
this.getController().sendData(serialMessage);
logger.warn(String.format("NODE %d: Item %s bit = %d Get current Indicator State", node.getNodeId(),
item.getName(), bit));
if (command instanceof State) {
this.getEventPublisher().postUpdate(item.getName(), (State) command);
}
}
/**
* {@inheritDoc}
*/
@Override
int getRefreshInterval() {
return REFRESH_INTERVAL;
}
}