Gentoo Forums
Gentoo Forums
Gentoo Forums
Quick Search: in
Control mpd with your phone
View unanswered posts
View posts from last 24 hours

 
Reply to topic    Gentoo Forums Forum Index Documentation, Tips & Tricks
View previous topic :: View next topic  
Author Message
TJNII
l33t
l33t


Joined: 09 Nov 2003
Posts: 637
Location: for(;;);

PostPosted: Sun Oct 14, 2007 6:05 am    Post subject: Control mpd with your phone Reply with quote

[Update]
I went ahead and hosted the script at http://tjnii.com/code/mpc_agi.phps
Remember to rename it to .php after downloading.
[/Update]


This is what I spent my Saturday night doing. I figured I'd share.

This Asterisk AGI lets you control mpd with your phone. It requires Asterisk and mpc. Download it, name it mpc_agi.php, chmod it +x, and plunk it down in your agi-bin directory. Point an extension at it and you're good to go.

This is version 0.1. Featureless but functional.

Usage (All numbers are followed by the pound key):
  • 1 - Play
  • 1 [number] - Play track [number]
  • 2 - Pause
  • 3 - Stop
  • 4 - Previous
  • 5 - Repeat
  • 6 - Next
  • 7 - Back 30 seconds
  • 7 [time] - Back [time]
  • 8 - random
  • 9 - Forward 30 seconds
  • 9 [time] - forward [time]

[time] is HH:MM:SS format. Not all 6 digits are required, 134 -> 00:01:34.

mpc_agi.php
Code:
#!/usr/bin/php -q
<?php
{
 
  // mpc_agi: Asterisk AGI to control MPD via MPC
  // V 0.1 10/13/07 TJNII
  // For the record I haven't coded in PHP in 2 years.  This may be dirty. -TJNII

  // TODO:
  // Make it say something on answer for phones where status is not obvious
  // Make it read off the song number / status
 
  // Debug
  $debug = false;

  // Variables and whatnot
  // mpc_command: Full path to the mpc binary
  $mpc_command = "/usr/bin/mpc";
   
  // mpc_argument: Array of arguments to use with mpc
  // The array key is matched to the first key the user presses.
  $mpc_argument[1] = "play";
  $mpc_argument[2] = "pause";
  $mpc_argument[3] = "stop";
  $mpc_argument[4] = "prev";
  $mpc_argument[5] = "repeat";
  $mpc_argument[6] = "next";
  $mpc_argument[7] = "seek";
  $mpc_argument[8] = "random";
  $mpc_argument[9] = "seek";
  // 0 is reserved for a future update.

  // default_extra: Default second argument.
  $default_extra[1] = "";
  $default_extra[2] = "";
  $default_extra[3] = "";
  $default_extra[4] = "";
  $default_extra[5] = "";
  $default_extra[6] = "";
  $default_extra[7] = "-00:00:30";
  $default_extra[8] = "";
  $default_extra[9] = "+00:00:30";

  // arg_extra: 0 if argument does not support anything extra
  //            1 if it supports the keyed value directly
  //            2 if it supports the argument in the form hh:mm:ss
  //            3 if it supports the argument in the form -hh:mm:ss
  //            4 if it supports the argument in the form +hh:mm:ss
  $arg_extra[1] = 1;
  $arg_extra[2] = 0;
  $arg_extra[3] = 0;
  $arg_extra[4] = 0;
  $arg_extra[5] = 0;
  $arg_extra[6] = 0;
  $arg_extra[7] = 3;
  $arg_extra[8] = 0;
  $arg_extra[9] = 4;


  // I stole the next few lines of code from the wakeup php AGI script

  GLOBAL $stdin, $stdout, $stdlog, $result, $parm_debug_on, $parm_test_mode;
 
  // These setting are on the WIKI pages http://www.voip-info.org
  ob_implicit_flush(false);
  set_time_limit(30);
  error_reporting(0);
 
  $stdin = fopen( 'php://stdin', 'r' );
  $stdout = fopen( 'php://stdout', 'w' );
  if($debug) {
    $stdlog = fopen('/tmp/mpc_agi.php', 'w'); // Mine
  }
 
  // ASTERISK * Sends in a bunch of variables, This accepts them and puts them in an array
  // http://www.voip-info.org/tiki-index.php?page=Asterisk%20AGI%20php

  // NOTE:  I don't use any of this, but its there and grabbed, which is good.
  while(!feof($stdin)) {   
    $temp = fgets( $stdin );
   
    if ($debug)
      fputs( $stdlog, $temp );
   
    // Strip off any new-line characters
    $temp = str_replace( "\n", "", $temp );
   
    $s = explode( ":", $temp );
    $agivar[$s[0]] = trim( $s[1] );
    if ( ( $temp == "") || ($temp == "\n") ) {   
      break;
    }
  }
 
  // End swiped code

  if($debug) {
    fputs($stdlog, "Script started.\n");
  }

  // Answer the call
  $ret_val = exec_command("ANSWER");
  if($ret_val["result"] != "0") {
    if($debug) {
      fputs($stdlog, "Answer returned " . $ret_val["result"]. ".\n");
      fflush($stdlog);
    }
    exit(1);
  }
  else {
    if($debug) {
      fputs($stdlog, "Answer returned " . $ret_val["result"]. ".\n");
    }
  }

  // Get the keypresses from the user
  $keypresses = get_digits("10000");

  // Did we get a command?
  if($keypresses["digits"] < 1) {
    // No command.  Quit.
    if($debug) {
      fputs($stdlog, "Exiting due to no keypress.\n");
      fflush($stdlog);
    }
    exit(0);
  }

  if($keypresses[0] == 0) {
    // Reserved.  Will be read mpc info.
    if($debug) {
      fputs($stdlog, "0 is reserved and unimplemented.  Exiting.\n");
      fflush($stdlog);
    }
    exit(0);
  }

  if($keypresses["digits"] == 1) {
    $full_command = $mpc_command . " " . $mpc_argument[$keypresses[0]]. " "
      . $default_extra[$keypresses[0]];
  }
  else {
    // We have an extra command.  Does this base command support it?
    switch($arg_extra[$keypresses[0]]) {
    case 0: {
      // No support.
      $full_command = $mpc_command . " " . $mpc_argument[$keypresses[0]]. " "
      . $default_extra[$keypresses[0]];
      break;
    }

    case 1: {
      // Direct support
      $full_command = $mpc_command . " " . $mpc_argument[$keypresses[0]]. " ";
     
      for($count = 1; $count < $keypresses["digits"]; $count += 1)
        $full_command .= $keypresses[$count];

      break;
    }

    case 2: {
      // Time (HH:MM:SS) support
      $full_command = $mpc_command . " " . $mpc_argument[$keypresses[0]]. " ";

      // I'm not going to require the user to punch in 6 digits. So:
      // 345 -> 00:03:45
      // 12345 -> 01:23:45
      // 4 -> 00:00:04
      // Etc.

      $added_digits = 0;

      // $keypresses["digits"] contains the command, so it will be (num digits) + 1
      for($count = 0; $count < (7 - $keypresses["digits"]); $count += 1) {
        $full_command .= "0";
        $added_digits += 1;
        if(($added_digits == 2) || ($added_digits == 4)) // % also matches 6, which we don't want.
          $full_command .= ":";
      }

      for($count = 1; $count < $keypresses["digits"]; $count += 1) {
        $full_command .= $keypresses[$count];
        $added_digits += 1;
        if(($added_digits == 2) || ($added_digits == 4)) // % also matches 6, which we don't want.
          $full_command .= ":";
      }
     
      break;
    }

    case 3: {
      // -Time (HH:MM:SS) support
      $full_command = $mpc_command . " " . $mpc_argument[$keypresses[0]]. " -";

      $added_digits = 0;

      // $keypresses["digits"] contains the command, so it will be (num digits) + 1
      for($count = 0; $count < (7 - $keypresses["digits"]); $count += 1) {
        $full_command .= "0";
        $added_digits += 1;
        if(($added_digits == 2) || ($added_digits == 4)) // % also matches 6, which we don't want.
          $full_command .= ":";
      }

      for($count = 1; $count < $keypresses["digits"]; $count += 1) {
        $full_command .= $keypresses[$count];
        $added_digits += 1;
        if(($added_digits == 2) || ($added_digits == 4)) // % also matches 6, which we don't want.
          $full_command .= ":";
      }
     
      break;
    }

    case 4: {
      // +Time (HH:MM:SS) support
      $full_command = $mpc_command . " " . $mpc_argument[$keypresses[0]]. " +";

      $added_digits = 0;

      // $keypresses["digits"] contains the command, so it will be (num digits) + 1
      for($count = 0; $count < (7 - $keypresses["digits"]); $count += 1) {
        $full_command .= "0";
        $added_digits += 1;
        if(($added_digits == 2) || ($added_digits == 4)) // % also matches 6, which we don't want.
          $full_command .= ":";
      }

      for($count = 1; $count < $keypresses["digits"]; $count += 1) {
        $full_command .= $keypresses[$count];
        $added_digits += 1;
        if(($added_digits == 2) || ($added_digits == 4)) // % also matches 6, which we don't want.
          $full_command .= ":";
      }
     
      break;
    }

    }
  }

  if($debug) {
    fputs($stdlog, "Executing: \"".$full_command."\"\n");
  }

  exec($full_command);

  if($debug) {
    fputs($stdlog, "Exiting successfully.\n");
    fflush($stdlog);
  }

  return 0;
}

// exec_command: send $command to Asterisk and return its response
function exec_command($command)
{
  GLOBAL $stdin, $stdout, $stdlog, $debug;

  // Output the command
  fputs($stdout, $command."\n");
  fflush($stdout);

  // Read the response
  $ret_val = fgets($stdin, 1024);
 
  if($ret_val == false) {
    // fgets failed.
    if($debug) {
      fputs($stdlog, "Exiting on fgets failure.\n");
      fflush($stdlog);
    }

    exit(1);
  }

  if($debug)
    fputs($stdlog, "Raw response: " . $ret_val . "\n");

  // Response is in a string form, that varies from command to command (Hork.)
  // Fortunately, someone else has already figured out how to handle this (Yay!)
  // So we will steal and modify their code, mmmkay?
  if ( preg_match("/^([0-9]{1,3}) (.*)/", $ret_val, $matches)) {   
    if (preg_match('/result=([-0-9a-zA-Z]*)(.*)/', $matches[2], $match)) {   
      $arr["code"] = $matches[1];
      $arr["result"] = $match[1];
      if (isset($match[3]) && $match[3])
        $arr["data"] = $match[3];
      if($debug)
        fputs($stdlog, "Returning Result: " . $arr["result"] . " Code: " . $arr["code"] . "\n");
      return $arr;
    }
    else {   
      if($debug)
        fputs($stdlog, "Couldn't figure out returned string, Returning code=$matches[1] result=0\n" );
      $arr["code"] = $matches[1];
      $arr["result"] = 0;
      return $arr;
    }
  }
  else {   
    if ($debug)
      fputs( $stdlog, "Could not process string, Returning -1\n" );
    $arr["code"] = -1;
    $arr["result"] = -1;
    return $arr;
  }
 
}

// get_digits: Get the numbers keyed by the user and return them in an array.
// timeout is in milliseconds
// Terminates on timeout, error, or non-numeric keypress.
function get_digits($timeout)
{

  GLOBAL $stdin, $stdout, $stdlog, $debug;

  $ret_val["digits"] = 0;

  $rval = exec_command("WAIT FOR DIGIT ".$timeout);
  $code = (integer)$rval["result"]; // MEMORY FOOTPRINT BE DAMNED!

  if($debug) {
    fputs($stdlog, "Sent: \"WAIT FOR DIGIT ".$timeout."\" Recieved: \"".$code."\"\n");
  }
 
  while(($code >= 48) && ($code <= 57)) {
    if($debug) {
      fputs($stdlog, "Saving code " . $code . " in slot " . $ret_val["digits"] . "\n");
    }

    $ret_val[$ret_val["digits"]] = ($code - 48);
    $ret_val["digits"] += 1;

    $rval = exec_command("WAIT FOR DIGIT ".$timeout);
    $code = (integer)$rval["result"];

    if($debug) {
      fputs($stdlog, "Sent: \"WAIT FOR DIGIT ".$timeout."\" Recieved: \"".$code."\"\n");
    }
  }

  if($debug) {
    fputs($stdlog, "get_digits() loop complete.\n");
  }

  return $ret_val;
}
?>


Entry in extensions.conf
(673 -> MPD)
Code:
; MPD control :P
exten => 673,1,agi,mpc_agi.php
exten => 673,2,Hangup()

[edited out the Wait(10) for V0.21 which loops]

So to play you would dial 673 (pause) 1# (hangup).
To play track 43 you would dial 673 (pause) 143# (hangup).
To stop you would dial 673 (pause) 3# (hangup).
Etc....

Enjoy. Future updates will make it say stuff, but I need to get festival working first. (Thus no support now.)


Last edited by TJNII on Sun Oct 14, 2007 9:32 pm; edited 2 times in total
Back to top
View user's profile Send private message
TJNII
l33t
l33t


Joined: 09 Nov 2003
Posts: 637
Location: for(;;);

PostPosted: Sun Oct 14, 2007 4:44 pm    Post subject: Reply with quote

Update: V0.2 is up. Last night (well, really early this morning) I was thinking it didn't support second options in the form [+/-]nnn which would be needed for volume control. My machine is hooked to an amp, so I use the amplifier volume control, which is why it wasn't in 0.1 and why the default keymapping doesn't have volume. So, let's go into how to remap your keys, since I made it easy to do!

How to remap the keys
Note: Currently this will only handle nine commands as the first key is the command key, */# are terminators and 0 is reserved.

This script generates mpc commands in the form /path/to/mpc arg0 arg1. To do this a number of arrays are defined: $mpc_argument, $default_extra, and $arg_extra. The key value of these arrays is the command keypress (the first key entered by the user). So command 2 points to [2], 5 to [5], etc.

arg0 is static for all commands and is contained in $mpc_argument. So if the user presses 1nnnn# arg0 will always be $mpc_argument[1], regardless of whatever nnnn is.

arg1 is dynamic based on what the user keys in. If the user enters a command only (i.e. 2#), arg1 is set to the default value. This is defined in $default_extra. For most commands it is blank, but it is used for seek in the default behavior. If the user enters a command and an argument (i.e. 2nnn#), the script looks to $arg_extra to see how it should interpret nnn. This is a numeric value a switch keys off of. See the comments for the specifics of this value.

Example
Let's say you want 7 and 9 to be volume up/down with a default volume change of 5. Let's say you also want 8 to be mute. These are the values you would change:

Set the commands on 7, 8, and 9 to volume:
$mpc_argument[7] = "volume";
$mpc_argument[8] = "volume";
$mpc_argument[9] = "volume";

Set the default arguments:
$default_extra[7] = "-5";
$default_extra[8] = "0";
$default_extra[9] = "+5";

Set the user argument behavior. (values accurate for V0.2)
$arg_extra[7] = 2; // 2: 7nnn# will become volume -nnn
$arg_extra[8] = 0; // 0: 8nnn# will ignore nnn
$arg_extra[9] = 3; // 3: 9nnn# will become volume +nnn

That's it. Got it?
Back to top
View user's profile Send private message
Display posts from previous:   
Reply to topic    Gentoo Forums Forum Index Documentation, Tips & Tricks All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum