/** * Global Sensor Networks (GSN) Source Code * Copyright (c) 2006-2016, Ecole Polytechnique Federale de Lausanne (EPFL) * * This file is part of GSN. * * GSN is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * GSN is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GSN. If not, see <http://www.gnu.org/licenses/>. * * File: src/ch/epfl/gsn/vsensor/VoipVirtualSensor.java * * @author sp3dy * @author Ali Salehi * @author Mehdi Riahi * */ /* * This virtual sensor implements a phone call notification. The virtual sensor makes a phone call when a condition * in the virtual sensor query is triggered, e.g. <query>SELECT temp FROM source1 WHERE temp >= 54 </query> * * The virtual sensor is configured to work with Asterisk [1]. So, you will need to install or have an existing asterisk * server running [2]. Once the virtual sensor is deployed, it automatically creates a dial plan in asterisk and registers * extensions needed to make the phone calls, minimal configuration is required. You need to add the following line to * the 'extensions_custom.conf' file in asterisk (/etc/asterisk/extensions_custom.conf): * * #include extensions_gsn.conf * * This will include the any extensions created by the virtual sensor in the file 'extensions_gsn.conf'. Depending on the * number of times the virtual sensor is deployed, an extension with the virtual sensor name, e.g. from the <virtual-sensor name="phone"> tag * and a random extension number (internal to asterisk) will be created. * * The virtual sensor uses the Manager API to connect (login) to asterisk and execute remote commands (e.g. load dial plan, * make phone call, call forward). To loging to the asterisk manager, you have to create an account and add your IP address in the 'manager.conf' * file in asterisk (/etc/asterisk/manager.conf): * * [testuser] * secret = mypassword * permit=192.168.12.101/255.255.0.0 * read = system,call,log,verbose,command,agent,user * write = system,call,log,verbose,command,agent,user * * Where, 'testuser' is the username and 'secret' is the password. In 'permit' add your IP address (otherwise asterisk will block you). * As part of asterisk, you will also need to install the 'festival' text to speech system [3] and the 'sox' utility [4]. These programs are * used by the virtual sensor to convert the notification message (string) to speech (audio) to be played by asterisk. If using linux/ubuntu, * do a 'apt-get install festival sox'. * * References * * [1] http://www.asterisk.org/ * [2] http://www.trixbox.org/downloads * [3] http://www.cstr.ed.ac.uk/projects/festival/ * [2] http://sox.sourceforge.net/ */ package ch.epfl.gsn.vsensor; import java.io.IOException; import java.util.Random; import java.util.TreeMap; import org.slf4j.LoggerFactory; import ch.epfl.gsn.beans.StreamElement; import ch.epfl.gsn.beans.VSensorConfig; import ch.epfl.gsn.vsensor.AbstractVirtualSensor; import org.slf4j.Logger; import org.asteriskjava.manager.ManagerConnection; import org.asteriskjava.manager.ManagerConnectionFactory; import org.asteriskjava.manager.TimeoutException; import org.asteriskjava.manager.action.OriginateAction; import org.asteriskjava.manager.response.ManagerResponse; /** * @author GSN Team */ public class VoipVirtualSensor extends AbstractVirtualSensor { private final static transient Logger logger = LoggerFactory.getLogger(VoipVirtualSensor.class); private ManagerConnection managerConnection; private OriginateAction originateAction; private ManagerResponse originateResponse; private boolean connected = false; private String vs_name; private String vs_ext; private String phone_no; private final int CALL_TIMEOUT = 17000; // in milliseconds // contains the extensions created by the virtual sensor, this file has to be added in the 'extensions_custom.conf' asterisk configuration private final String DIAL_PLAN = "extensions_gsn.conf"; // The default configuratoin can only made SIP calls to internal SIP/IP phones registered with asterisk. // To make external PSTN calls, change the SIP trunk name to match the VoIP account provider. private final String SIP_TRUNK = "from-internal"; private static int vs_counter = 0; private int NumberOfCalls = 0; private boolean CallAnswered = false; private final int NUMBER_OF_RETRY_CALLS = 50; public void dataAvailable(String inputStreamName, StreamElement data) { if (connected) { // post the stream data produced dataProduced(data); // the query in the virtual sensor was satisfied make a phone call logger.info("Query Satisfied - Calling phone number " + phone_no); logger.info("CallAnswered=" + CallAnswered + " NumberOfCalls=" + NumberOfCalls); try { // to prevent multiple calls if (CallAnswered == false || NumberOfCalls == NUMBER_OF_RETRY_CALLS) { // generate the call and wait for answer or timeout originateResponse = managerConnection.sendAction(originateAction, CALL_TIMEOUT); if(originateResponse.getResponse().equals("Success")) { logger.info("Call answered."); CallAnswered = true; } else if (originateResponse.getResponse().equals("Error")) { logger.error("Call Error - Wrong phone number or extension."); CallAnswered = false; } NumberOfCalls = 0; } } catch (TimeoutException e) { logger.warn("Call timeout (-" + CALL_TIMEOUT + ") milliseconds."); NumberOfCalls = 0; } catch (IOException e) { logger.warn(e.getMessage()); } NumberOfCalls++; } } public boolean initialize() { VSensorConfig vsensor = getVirtualSensorConfiguration(); TreeMap<String, String> params = vsensor.getMainClassInitialParams(); ManagerConnectionFactory factory = new ManagerConnectionFactory(params.get("host"), params.get("username"), params.get("password")); managerConnection = factory.createManagerConnection(); // get the name of the virtual sensor from the vsd vs_name = new String(vsensor.getName()); // generate a random extension number between 9000-10000 Random random = new Random(); Integer ext = (int)((long)(1001 * random.nextDouble()) + 9000); vs_ext = ext.toString(); try { // connect to Asterisk and log in managerConnection.login(); connected = true; // delete previous configuration, e.g. dial plan and config files cleanConfig(); // create the text-to-speech ulaw file text2speech_low(params.get("message")); // create the dial plan in the asterisk server createDialPlan(vs_name, vs_ext); // settings for making the actual phone call originateAction = new OriginateAction(); phone_no = new String(params.get("number")); originateAction.setChannel("SIP/" + phone_no + "@" + SIP_TRUNK); originateAction.setContext(vs_name); originateAction.setExten(vs_ext); originateAction.setPriority(new Integer(1)); originateAction.setCallerId("GSN Notification"); } catch (Exception e) { connected = false; logger.error("connection state is " + managerConnection.getState() + " "+ e); } vs_counter++; logger.info("Virtual Sensor [" + vs_name + "]" + " added to GSN with extension " + vs_ext + " running instance @" + vs_counter); return connected; } public void dispose() { if (connected) { try { // delete the audio files from asterisk server and logoff from the manager. originateAction = new OriginateAction(); originateAction.setChannel("Local/1004@dummy-wait"); originateAction.setApplication("System"); if (vs_counter == 0) { originateAction.setData("rm -rf /etc/asterisk/" + DIAL_PLAN); originateResponse = managerConnection.sendAction(originateAction, CALL_TIMEOUT); logger.info("dispose() : Removed " + DIAL_PLAN + " from Asterisk configuration. " + originateResponse.getResponse()); // and finally log off and disconnect managerConnection.logoff(); } else { // delete only the ulaw files for the virtual sensor originateAction.setData("rm -rf /tmp/gsn_tmp /tmp/" + vs_name + ".ulaw" + " /tmp/" + vs_name + ".gsm"); originateResponse = managerConnection.sendAction(originateAction, CALL_TIMEOUT); logger.info("dispose() : Removed /tmp/gsn_tmp /tmp/" + vs_name + ".ulaw files." + " " + originateResponse.getResponse()); // and finally log off and disconnect managerConnection.logoff(); // decrease wrapper vs_counter vs_counter--; } } catch (Exception e) { logger.error("Error removing the virtual sensor." + e); } logger.info("dispose() : Virtual Sensor removed [" + vs_name + "]."); } } public void createDialPlan(String vs_name, String vs_ext) { try { originateAction = new OriginateAction(); originateAction.setChannel("Local/1004@dummy-wait"); originateAction.setApplication("System"); originateAction.setData("echo -e '[" + vs_name + "]\nexten => " + vs_ext + ",1,Answer\nexten => " + vs_ext + ",2,Wait(1)\nexten => " + vs_ext + ",3,Playback(/tmp/" + vs_name + ", skip)\nexten => " + vs_ext + ",4,Wait(1)\nexten => " + vs_ext + ",5,Hangup\n' >> /etc/asterisk/" + DIAL_PLAN + " ; /usr/sbin/asterisk -rx reload"); originateResponse = managerConnection.sendAction(originateAction, CALL_TIMEOUT); logger.info("createDialPlan() : " + originateResponse.getResponse()); } catch (Exception e) { logger.warn(e.getMessage()); } } public void text2speech_low(String msg) { originateAction = new OriginateAction(); originateAction.setChannel("Local/1004@dummy-wait"); originateAction.setApplication("System"); originateAction.setData("echo '" + msg + "' | text2wave -scale 4 -o /tmp/" + vs_name + ".ulaw -otype ulaw"); try { originateResponse = managerConnection.sendAction(originateAction, CALL_TIMEOUT); logger.info("text2speech_low() : " + originateResponse.getResponse()); } catch (Exception e) { logger.warn(e.getMessage()); } } public void cleanConfig() { try { // delete the audio files from asterisk server and logoff from the manager. originateAction = new OriginateAction(); originateAction.setChannel("Local/1004@dummy-wait"); originateAction.setApplication("System"); if (vs_counter == 0) { originateAction.setData("rm -rf /tmp/*.ulaw /etc/asterisk/" + DIAL_PLAN); originateResponse = managerConnection.sendAction(originateAction, CALL_TIMEOUT); logger.info("cleanConfig() : Removed previous " + DIAL_PLAN + " and ulaw files from Asterisk configuration. " + originateResponse.getResponse()); } } catch (Exception e) { logger.error("Error cleaning previous configuration for the virtual sensor. " + e); } } }