June 19, 2006

Asterisk, PHP, and iTunes?

Asterisk - PHP - iTunes

What do these three things have anything in common? Independently, they are all extremely useful. Together, they produce an auto-play-pause-caller-id-phone-system.

The idea is this. I use iTunes to play music on my Mac. I listen to it all day long. The problem is that I get lots of phone calls and am always finding myself either pausing iTunes or more likely muting the computer. The issue arises when three hours has past since my last phone call and my computer's music is still muted. I miss all of my precious music.

The solution. Hook up iTunes to know when I answer, make and hang-up calls. Luckily, I use a Mac. OS X has php pre-installed and makes producing software easy. Also, Asterisk, the open source PBX upon which our phone system runs, has an awesome API. The API allows us to login to their system via a TCP socket and monitor all activity.

So how does it all tie together?

When my computer boots, I have it start a php script that connects to Asterisk and demands it send all call activity. PHP then monitors this activity and filters out only activity for my phone. When PHP detects that a phone call has been made, the information is passed to an AppleScript that triggers iTunes to pause and a dialog to appear stating "Outgoing call". This was made easy by use of Growl. I did the same thing for incoming calls except I show the caller id info for that. Once I hang up, iTunes is called to continue playing my playlist.

There were a few obstacles that needed to be overcome. When I was on the phone and someone else called in and hung up, my music began blasting again. The person I was on the phone with at the time was like "What is that?" I reprogrammed my script to use a stack to make sure it will only play music once all my calls were hung up. Another obstacle was getting the correct information to read. Although the Asterisk interface is extremely powerful, it is hard to get to know how to correctly monitor phone activity. The final stumbling block was getting iTunes to only respond if it was open. AppleScript does not have a "tell application if open" command. You'll see my workaround below.

Want some code? You are free to use the code below to do that you want. Just give me some credit as outlined in the copyright below.

mon.php
#!/usr/bin/php -q
<?php 

// Copyright (c) 2006. RustyBrick, Inc.  http://www.rustybrick.com/
// You are permitted to do what you want with this code assuming you leave this
// copyright here and ask for permission in any non-private usage.  For more info,
// please email info@rustybrick.com

// script needs exec rights or alternatively can be run via "php script.php"
// pass the arguments for server's ip address and port, your extension, 
//  and the unique channel your phone belongs to.

$server 	= $argv[1];
$port 		= $argv[2];
$extension 	= $argv[3];
$channel 	= $argv[4];

for ($i=4;$i<=$argc;$i++) {
	$channels[] = $argv[$i];
}

if (!$extension) {
	die("Usage: mon.php server port extension channel ...\r\n");
}

// connect to Asterisk server

$socket = fsockopen($server, $port, $errno, $errstr, 5); 

if (!$socket) {
	die("Error connecting $errno $errstr\r\n");
}


// doesn't work for some reason

register_shutdown_function('logout',$socket);

function logout($socket)
{
	fputs($socket, "Action: Logoff\r\n\r\n");
	fclose($socket);
}

if ($socket) {
	
	// You will have needed to add your login/secret info to the manager.conf file on asterisk.
	
	fputs($socket, "Action: Login\r\n"); 
	fputs($socket, "UserName: extenwatch\r\n"); // replace extenwatch with your username
	fputs($socket, "Secret: secret\r\n");			// replace secret with your secret code
	fputs($socket, "Events: call\r\n\r\n");		// just monitor call data
	
	$event = '';
	$stack = 0;	
	
	// Keep a loop going to read the socket and parse the resulting commands.

	while (!feof($socket)) {
		$buffer = fgets($socket, 4096);
		
		if ($buffer == "\r\n") {	// handle partial packets
			$event_started = false;
			
			// parse the event and get the result hashtable
			
			$e = getEvent($event);
			
			// filter out useless info
			
			if (!$e['Uniqueid'] || !$e['Event'])
				continue;
			
			$is_listen_channel = false;
			
			foreach ($channels as $channel) {
				if (stristr($e['Channel'],$channel)) {
					$is_listen_channel = true;
					break;
				}
			}
			
			if (!$is_listen_channel)
				continue;

			// end filter
			
			// handle new calls

			if ($e['Event'] == 'Newchannel' && stristr($e['State'],'Ring')) {				
				if (strstr($e['CallerID'],"<$extension>")) {
					exec("osascript outgoing.scpt");					
				}
				else {
					exec("osascript incoming.scpt '".addslashes($e['CallerID'])."'");
				}
				
				++$stack;
			}
			
			// handle hangups
			
			else if ($e['Event'] == 'Hangup') {
				--$stack;
				
				// only play itunes when all calls hung up
				
				if ($stack == 0) {
					exec("osascript hangup.scpt");
				}
			}
			
			$event = '';
		}
		
		// handle partial packets
		
		if ($event_started) {
			$event .= $buffer;
		}
		else if (strstr($buffer,'Event:')) {
			$event = $buffer;
			$event_started = true;
		}
	}
}

// go through and parse the event
// returning a nice hashtable for accessing the data

function getEvent($event)
{
	$event_params = explode("\n",$event);
	
	foreach ($event_params as $event) {
		list($key,$val) = explode(": ",$event);
		
		$key = trim($key);
		$val = trim($val);
		
		if ($key)
			$e[$key] = $val;
	}
	
	return($e);
}

?>


incoming.scpt
on run caller_id
	tell application "GrowlHelperApp"
		set the allNotificationsList to {"Phone Notification"}
		set the enabledNotificationsList to {"Phone Notification"}
		set the_user to the second word of (characters (offset of "Users" in path to ¬
			preferences as string) through (length of (path to preferences as string)) of ¬
			(path to preferences as string) as string)
		
		register as application ¬
			"Asterisk AppleScript" all notifications allNotificationsList ¬
			default notifications enabledNotificationsList ¬
			icon of application "Script Editor"
		
		notify with name ¬
			"Phone Notification" title ¬
			"Incoming Call" description ¬
			"From: " & caller_id application name ¬
			"Asterisk AppleScript" image from location ¬
			"file:///Users/" & the_user & "/asterisk_mon/incoming.gif"
		
	end tell
	
	tell application "System Events"
		set iTunesRunning to (name of processes) contains "iTunes"
	end tell
	
	if iTunesRunning = true then
		tell application "iTunes" to pause
	end if
	
end run


outgoing.scpt
tell application "GrowlHelperApp"
	set the allNotificationsList to {"Phone Notification"}
	set the enabledNotificationsList to {"Phone Notification"}
	set the_user to the second word of (characters (offset of "Users" in path to ¬
		preferences as string) through (length of (path to preferences as string)) of ¬
		(path to preferences as string) as string)
	
	register as application ¬
		"Asterisk AppleScript" all notifications allNotificationsList ¬
		default notifications enabledNotificationsList ¬
		icon of application "Script Editor"
	
	notify with name ¬
		"Phone Notification" title ¬
		"Outgoing Call" description ¬
		"Placing call..." application name ¬
		"Asterisk AppleScript" image from location ¬
		"file:///Users/" & the_user & "/asterisk_mon/outgoing.gif"
end tell

tell application "System Events"
	set iTunesRunning to (name of processes) contains "iTunes"
end tell

if iTunesRunning = true then
	tell application "iTunes" to pause
end if


hangup.scpt
tell application "System Events"
	set iTunesRunning to (name of processes) contains "iTunes"
end tell

if iTunesRunning = true then
	tell application "iTunes" to play
end if
Posted by Ronnie (CTO) at June 19, 2006 8:49 AM | TrackBack
Comments

I love this script, works wonders for me.

Posted by: Barry Schwartz at June 19, 2006 11:43 AM
TrackBack
TrackBack URL for this entry:


Listed below are links to weblogs that reference 'Asterisk, PHP, and iTunes?' from RustyBrick Web Technology Blog.

Post a comment









Remember personal info?






The RustyBrick Web Technology Blog is sponsored by RustyBrick, Inc. - Web Development Services