/**
* 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.asterisk.internal;
import java.io.IOException;
import java.util.Collection;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.apache.commons.lang.StringUtils;
import org.asteriskjava.manager.AuthenticationFailedException;
import org.asteriskjava.manager.ManagerConnection;
import org.asteriskjava.manager.ManagerConnectionFactory;
import org.asteriskjava.manager.ManagerEventListener;
import org.asteriskjava.manager.action.StatusAction;
import org.asteriskjava.manager.event.DtmfEvent;
import org.asteriskjava.manager.event.HangupEvent;
import org.asteriskjava.manager.event.ManagerEvent;
import org.asteriskjava.manager.event.NewChannelEvent;
import org.openhab.binding.asterisk.AsteriskBindingProvider;
import org.openhab.binding.asterisk.internal.AsteriskGenericBindingProvider.AsteriskBindingConfig;
import org.openhab.core.binding.AbstractBinding;
import org.openhab.core.items.Item;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.library.tel.items.CallItem;
import org.openhab.library.tel.types.CallType;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The Asterisk binding connects to a Manager Interface of an Asterisk VOIP PBX
* and listens to event notifications from this box.
*
* @author Thomas.Eichstaedt-Engelen
* @since 0.9.0
*/
public class AsteriskBinding extends AbstractBinding<AsteriskBindingProvider> implements ManagedService {
private static final Logger logger = LoggerFactory.getLogger(AsteriskBinding.class);
protected static ManagerConnection managerConnection;
/** The hostname of the Asterisk Manager Interface to connect to */
protected static String host;
/** The username to connect to the Manager Interface */
protected static String username;
/** The password to connect to the Manager Interface */
protected static String password;
public void activate() {
}
public void deactivate() {
disconnect();
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("rawtypes")
public void updated(Dictionary config) throws ConfigurationException {
if (config == null) {
return;
}
disconnect();
AsteriskBinding.host = Objects.toString(config.get("host"), null);
AsteriskBinding.username = Objects.toString(config.get("username"), null);
AsteriskBinding.password = Objects.toString(config.get("password"), null);
if (StringUtils.isNotBlank(AsteriskBinding.host) && StringUtils.isNotBlank(AsteriskBinding.username)) {
connect(AsteriskBinding.host, AsteriskBinding.username, AsteriskBinding.password);
} else {
logger.warn("Cannot connect to Asterisk manager interface because of missing "
+ "parameters (host={}, username={})", AsteriskBinding.host, AsteriskBinding.username);
}
}
/**
* Connects to <code>host</code> and opens a ManagerConnection by using the
* given <code>username</code> and <code>password</code>. Note: The Asterisk
* ManagerInterface on your Asterisk PBX is deactivated by default. Please
* refer to the documentation how to activate the ManagerInterface (AMI).
*
* @param host where to find the Asterisk PBX
* @param username username to login to Asterisk ManagerInterface
* @param password password to login to Asterisk ManagerInterface
*/
private void connect(String host, String username, String password) {
ManagerConnectionFactory factory = new ManagerConnectionFactory(host, username, password);
AsteriskBinding.managerConnection = factory.createManagerConnection();
AsteriskBinding.managerConnection.addEventListener(new AsteriskEventManager());
try {
AsteriskBinding.managerConnection.login();
} catch (AuthenticationFailedException afe) {
logger.error("Authentication failed. Please verify username and password.");
} catch (IOException ioe) {
logger.error("Could not connect to manager interface on host {}: {}", host, ioe);
} catch (Exception e) {
logger.error("Login to Asterisk manager interface on host {} threw an exception: {}", host, e);
}
try {
AsteriskBinding.managerConnection.sendAction(new StatusAction());
} catch (Exception e) {
logger.error("Registering for status update threw an exception: {}", e);
}
}
/**
* Disconnects from the current Asterisk ManagerInterface.
*/
private void disconnect() {
if (AsteriskBinding.managerConnection != null) {
AsteriskBinding.managerConnection.logoff();
AsteriskBinding.managerConnection = null;
}
}
/**
* @author Thomas.Eichstaedt-Engelen
*/
private class AsteriskEventManager implements ManagerEventListener {
/** holds call details of the currently active calls */
protected Map<String, CallType> eventCache;
public AsteriskEventManager() {
eventCache = new HashMap<String, CallType>();
}
/**
* @{inheritDoc}
*/
public void onManagerEvent(ManagerEvent managerEvent) {
for (AsteriskBindingProvider provider : providers) {
for (String itemName : provider.getItemNames()) {
Class<? extends Item> itemType = provider.getItemType(itemName);
AsteriskBindingConfig config = (AsteriskBindingConfig) provider.getConfig(itemName);
handleManagerEvent(itemName, itemType, managerEvent, config);
}
}
}
/**
* Dispatches the given <code>managerEvent</code> to the specialized
* handler methods.
*
* @param itemName the corresponding item
* @param itemType the Type of the corresponding item
* @param managerEvent the {@link ManagerEvent} to dispatch
*/
private void handleManagerEvent(String itemName, Class<? extends Item> itemType, ManagerEvent managerEvent,
AsteriskBindingConfig config) {
if (managerEvent instanceof NewChannelEvent) {
handleNewCall(itemName, itemType, (NewChannelEvent) managerEvent, config);
} else if (managerEvent instanceof HangupEvent) {
handleHangupCall(itemName, itemType, (HangupEvent) managerEvent, config);
} else if (managerEvent instanceof DtmfEvent) {
handleDtmfEvent(itemName, itemType, (DtmfEvent) managerEvent, config);
}
}
private void handleDtmfEvent(String itemName, Class<? extends Item> itemType, DtmfEvent event,
AsteriskBindingConfig config) {
if (config.type.equals("digit") && event.isBegin()) {
CallType call = eventCache.get(event.getUniqueId());
if (call != null) {
String reqCid = config.getCallerId();
String reqExt = config.getExtension();
String reqDigit = config.getDigit();
String src = null;
String dst = null;
if (event.getDirection().toString().equals("Sent")) {
src = call.getDestNum().toString();
dst = call.getOrigNum().toString();
} else {
src = call.getOrigNum().toString();
dst = call.getDestNum().toString();
}
if ((reqCid == null || (reqCid != null && reqCid.equals(src)))
&& (reqExt == null || (reqExt != null && reqExt.equals(dst)))
&& (reqDigit.equals(event.getDigit().toString()))) {
if (itemType.isAssignableFrom(SwitchItem.class)) {
eventPublisher.postUpdate(itemName, OnOffType.ON);
} else {
logger.warn("DTMF event not applicable to item {}", itemName);
}
}
logger.info("DTMF event received. Digit '{}' sent from '{}' to '{}'", event.getDigit(), src, dst);
}
}
}
private void handleNewCall(String itemName, Class<? extends Item> itemType, NewChannelEvent event,
AsteriskBindingConfig config) {
if (event.getCallerIdNum() == null || event.getExten() == null) {
logger.debug("calleridnum or exten is null -> handle new call aborted!");
return;
}
CallType call = new CallType(new StringType(event.getCallerIdNum()), new StringType(event.getExten()));
eventCache.put(event.getUniqueId(), call);
if (config.type.equals("active")) {
String reqCid = config.getCallerId();
String reqExt = config.getExtension();
if ((reqCid == null || (reqCid != null && reqCid.equals(event.getCallerIdNum().toString())))
&& (reqExt == null || (reqExt != null && reqExt.equals(event.getExten().toString())))) {
if (itemType.isAssignableFrom(SwitchItem.class)) {
eventPublisher.postUpdate(itemName, OnOffType.ON);
} else if (itemType.isAssignableFrom(CallItem.class)) {
eventPublisher.postUpdate(itemName, call);
} else {
logger.warn("Handle call for item '{}' is undefined", itemName);
}
}
}
}
/**
* Removes <code>event</code> from the <code>eventCache</code> and posts
* updates according to the content of the <code>eventCache</code>. If
* there is no active call left we send an OFF-State (resp. empty)
* {@link CallType} and ON-State (one of the remaining active calls)
* in all other cases.
*
* @param itemName
* @param itemType
* @param event
*/
private void handleHangupCall(String itemName, Class<? extends Item> itemType, HangupEvent event,
AsteriskBindingConfig config) {
eventCache.remove(event.getUniqueId());
if (config.type.equals("active")) {
String reqCid = config.getCallerId();
String reqExt = config.getExtension();
if (reqCid == null && reqExt == null) { // if both requirements are null, toggle the switch or call the
// old way
if (itemType.isAssignableFrom(SwitchItem.class)) {
OnOffType activeState = (eventCache.size() == 0 ? OnOffType.OFF : OnOffType.ON);
eventPublisher.postUpdate(itemName, activeState);
} else if (itemType.isAssignableFrom(CallItem.class)) {
CallType call = (CallType) (eventCache.size() == 0 ? CallType.EMPTY
: eventCache.values().toArray()[0]);
eventPublisher.postUpdate(itemName, call);
} else {
logger.warn("handleHangupCall - postUpdate for item '{}' is undefined", itemName);
}
} else {
if ((reqCid == null || (reqCid != null && reqCid.equals(event.getCallerIdNum().toString())))
&& (reqExt == null || (reqExt != null && reqExt.equals(event.getExten().toString())))) {
if (itemType.isAssignableFrom(SwitchItem.class)) {
eventPublisher.postUpdate(itemName, OnOffType.OFF);
} else if (itemType.isAssignableFrom(CallItem.class)) {
eventPublisher.postUpdate(itemName, CallType.EMPTY);
} else {
logger.warn("handleHangupCall - postUpdate for item '{}' is undefined", itemName);
}
}
}
}
}
}
}