/**
* 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.rfxcom.internal;
import java.io.IOException;
import java.util.EventObject;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.xml.bind.DatatypeConverter;
import org.openhab.binding.rfxcom.RFXComBindingProvider;
import org.openhab.binding.rfxcom.RFXComValueSelector;
import org.openhab.binding.rfxcom.internal.connector.RFXComEventListener;
import org.openhab.binding.rfxcom.internal.connector.RFXComSerialConnector;
import org.openhab.binding.rfxcom.internal.messages.RFXComBaseMessage.PacketType;
import org.openhab.binding.rfxcom.internal.messages.RFXComMessageFactory;
import org.openhab.binding.rfxcom.internal.messages.RFXComMessageInterface;
import org.openhab.binding.rfxcom.internal.messages.RFXComTransmitterMessage;
import org.openhab.core.binding.AbstractBinding;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* RFXComBinding listens to RFXCOM controller notifications and post values to
* the openHAB event bus when data is received and post item updates
* from openHAB internal bus to RFXCOM controller.
*
* @author Pauli Anttila, Evert van Es
* @since 1.2.0
*/
public class RFXComBinding extends AbstractBinding<RFXComBindingProvider> {
private static final Logger logger = LoggerFactory.getLogger(RFXComBinding.class);
private EventPublisher eventPublisher;
private static final int timeout = 5000;
private static byte seqNbr = 0;
private final ResultRegistry resultRegistry = new ResultRegistry();
private final MessageLister eventLister = new MessageLister();
public RFXComBinding() {
}
@Override
public void activate() {
logger.debug("Activate");
RFXComSerialConnector connector = RFXComConnection.getCommunicator();
if (connector != null) {
connector.addEventListener(eventLister);
}
}
@Override
public void deactivate() {
logger.debug("Deactivate");
RFXComSerialConnector connector = RFXComConnection.getCommunicator();
if (connector != null) {
connector.removeEventListener(eventLister);
}
}
@Override
public void setEventPublisher(EventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
@Override
public void unsetEventPublisher(EventPublisher eventPublisher) {
this.eventPublisher = null;
}
/**
* @{inheritDoc
*/
@Override
protected void internalReceiveCommand(String itemName, Command command) {
logger.debug("Received command: {} {}", itemName, command);
if (itemName != null) {
if (executeCommand(itemName, command) && command instanceof State) {
eventPublisher.postUpdate(itemName, (State) command);
}
}
}
/**
* Find the first matching {@link RFXComBindingProvider} according to
* <code>itemName</code> and <code>command</code>.
*
* @param itemName
*
* @return the matching binding provider or <code>null</code> if no binding
* provider could be found
*/
private RFXComBindingProvider findFirstMatchingBindingProvider(String itemName) {
RFXComBindingProvider firstMatchingProvider = null;
for (RFXComBindingProvider provider : this.providers) {
String Id = provider.getId(itemName);
if (Id != null) {
firstMatchingProvider = provider;
break;
}
}
return firstMatchingProvider;
}
public static synchronized byte getSeqNumber() {
return seqNbr;
}
public synchronized byte getNextSeqNumber() {
if (++seqNbr == 0) {
seqNbr = 1;
}
return seqNbr;
}
/**
*
* @return true if the command was successfully sent, false otherwise
*/
private boolean executeCommand(String itemName, Type command) {
final RFXComBindingProvider provider = findFirstMatchingBindingProvider(itemName);
if (provider == null) {
logger.warn("Cannot execute command because no binding provider was found for itemname '{}'", itemName);
return false;
}
if (!provider.isInBinding(itemName)) {
logger.debug("Received command (item='{}', state='{}', class='{}')",
new Object[] { itemName, command.toString(), command.getClass().toString() });
RFXComSerialConnector connector = RFXComConnection.getCommunicator();
if (connector == null) {
logger.warn("RFXCom controller is not initialized!");
return false;
}
if (!connector.isConnected()) {
logger.warn("RFXCom controller is not connected");
return false;
}
return executeCommand0(itemName, command, provider, connector);
} else {
logger.warn("Provider is not in binding '{}'", provider.toString());
return false;
}
}
private boolean executeCommand0(String itemName, Type command, final RFXComBindingProvider provider,
RFXComSerialConnector connector) {
String id = provider.getId(itemName);
PacketType packetType = provider.getPacketType(itemName);
Object subType = provider.getSubType(itemName);
RFXComValueSelector valueSelector = provider.getValueSelector(itemName);
final Future<RFXComTransmitterMessage> result;
try {
RFXComMessageInterface obj = RFXComMessageFactory.getMessageInterface(packetType);
final byte seqNumber = getNextSeqNumber();
obj.convertFromState(valueSelector, id, subType, command, seqNumber);
byte[] data = obj.decodeMessage();
logger.debug("Transmitting data: {}", DatatypeConverter.printHexBinary(data));
result = resultRegistry.registerCommand(seqNumber);
connector.sendMessage(data);
} catch (IOException e) {
logger.error("Message sending to RFXCOM controller failed.", e);
return false;
} catch (RFXComException e) {
logger.error("Message sending to RFXCOM controller failed.", e);
return false;
}
boolean success = false;
try {
final RFXComTransmitterMessage resp = result.get(timeout, TimeUnit.MILLISECONDS);
switch (resp.response) {
case ACK:
case ACK_DELAYED:
logger.debug("Command succesfully transmitted, '{}' received", resp.response);
success = true;
break;
case NAK:
case NAK_INVALID_AC_ADDRESS:
case UNKNOWN:
logger.error("Command transmit failed, '{}' received", resp.response);
break;
}
} catch (InterruptedException e) {
logger.error("No acknowledge received from RFXCOM controller, timeout {}ms due to", timeout, e);
} catch (ExecutionException e) {
logger.error("No acknowledge received from RFXCOM controller, timeout {}ms due to {}", timeout, e);
} catch (TimeoutException e) {
logger.error("No acknowledge received from RFXCOM controller, timeout {}ms due to {}", timeout, e);
}
return success;
}
private class MessageLister implements RFXComEventListener {
@Override
public void packetReceived(EventObject event, byte[] packet) {
try {
RFXComMessageInterface obj = RFXComMessageFactory.getMessageInterface(packet);
if (obj instanceof RFXComTransmitterMessage) {
RFXComTransmitterMessage resp = (RFXComTransmitterMessage) obj;
resultRegistry.responseReceived(resp);
} else {
final String deviceId = obj.generateDeviceId();
final List<RFXComValueSelector> supportedValueSelectors = obj.getSupportedValueSelectors();
if (supportedValueSelectors != null) {
for (RFXComBindingProvider provider : providers) {
for (String itemName : provider.getItemNames()) {
String id1 = provider.getId(itemName);
boolean inBinding = provider.isInBinding(itemName);
if (id1.equals(deviceId) && inBinding) {
RFXComValueSelector valueSelector = provider.getValueSelector(itemName);
if (supportedValueSelectors.contains(valueSelector)) {
try {
State value = obj.convertToState(valueSelector);
eventPublisher.postUpdate(itemName, value);
} catch (RFXComException e) {
logger.warn("Data conversion error", e);
}
}
}
}
}
}
}
} catch (RFXComException e) {
logger.error("Error occured during packet receiving, data: {}",
DatatypeConverter.printHexBinary(packet), e);
}
}
}
}