📜 ⬆️ ⬇️

We write processing Asterisk AMI do it yourself. Part one: create a class in PHP to refer to an asterisk or how to make a php socket client yourself

Instead of the preface


Greetings to the community!

My first article Example of implementing an appeal to Asterisk CLI in PHP. Structuring the answer asterisks finally passed from moderation to the sandbox and the moderator immediately gave me full rights to publish to Habré.

I want to start a series of articles on how you can implement your own API for handling Asterisk through AMI. If you have the time, the desire and the most important experience, support me and try to form a team that on this portal will create a full-fledged php API for interacting with AMI Asterisk, we are talking to the dialogue.
')
The task is very interesting since many services dedicated to Asterisk immediately hide all the information about attempts to develop their own interfaces for Asterisk. Although my experience, which I will explain here, can be an attempt to entertain my “ego” for many “kodders”, but creating your own web application that will not fail every second will not create options (although if you are a javascript virtuoso, you’ll even have some literate substitute for bourgeois web muzzle for aster).

In this part, I will show that writing your own class to refer to the AMI asterisk is easy, the main thing is to go from simple to complex.

For especially lazy at the end of the article there is a ready (working) module with the class Asterisk AMI . With examples of requests. The class is fully working and ready to use.

Materiel


I decided to implement the mechanism of the socket on fsockopen.

Before you start coding a little educational program.

Functions and keywords php occurring in the code
.
__destruct
Destructor class. The main task of the destructor is to perform user-defined actions when a class instance is destroyed (in any case, even if the script crashes).

: function __destruct() { echo " "; //    . } 


count
Returns the number of elements in the array.
 : var $a = array ("a","b","c"); echo count($a); // count  3 ( ) 


die
The completion of the script at this point and the output line. Usually used to stop the script in the script body.
 : die (" "); //      : " " 


explode
Splits a string into an array using a marker.
 : $a = explode(" ","   "); // : $a[0]="", $a[1]="", $a[2]="", $a[3]="" 


fclose
Closes the handle associated with the file / socket.
 : fclose($a); //    /   $a  


fread
Reads data from a file / socket.
 : $b = fread($a,100); //  100   /   $a  


fsockopen
Opens connection to the socket.
 : $m= fsockopen(host, port,$a,$b,2); // $m   ()     false     . 


function
Function declaration in php.
 : function example() { echo "function"; } //   example 


fwrite
Write to file / socket using binary-safe mode.
 : fwrite($a, "  "); //   /   $a  "  " 


public
Declaring variables, constants, functions inside a class publicly available. Everything that is declared public can be accessed from the main code.
 : class TempClass { public $a = '       '; } $b = new TempClass(); echo $b->a; //   '        


rand
Produces a random integer. If the maximum and minimum values ​​are specified, the number will be within the specified range.
 : echo rand(100,999); //     100  999 


return
Generates a function exit. If after return there is a value / variable / any object, then the function returns the given entity.
 : function ad () { return 5; } echo ad(); //  5 


set_time_limit
Sets the maximum execution time of the script in seconds. If the parameter contains the number 0, then the maximum time is reset and the script can run forever until the algorithm inside it completes its execution.
 : set_time_limit (10); echo ad(); //     5 . 


sleep
Suspends the execution of the script in seconds.
 : sleep (10); echo " ,    "; //      " ,    "  10 . 


socket_get_status
Extracts metadata from a socket
 : socket_get_status ($a); //  ()     $a. 


stream_set_timeout
Sets the timeout value for a socket
 : stream_set_timeout ($a,0,1000); //        $a. 


trim
Removes spaces at the beginning and end of a line.
 : echo trim ("     "); //   "   "; 


var_dump
Gives the entire structure
 : var_dump(5); //  int(5); 


->
Reversal to objects defined inside the class
 : class TempClass { public $a = '   '; } $b = new TempClass(); echo $b->a; //   '   ' 


 *    ,      ,         .          .                ,     .      . 


We get the login and password for Asterisk AMI using WinSCP
1. Downloading WinSCP from the official website



2. Run the program and go to the server filling in the fields indicated with a red marker.



Click "yes" to all questions and if you need to re-enter the password.

3. Next, look for the file manager.conf. The default location for the / etc / asterisk / file.



Open the file by double clicking.

4. Inside the file we find the section [admin]



We write down the login and password from your Asterisk AMI. In my case, this is admin and amp11.

Learning to connect via SSH tunnel to Asterisk AMI using putty
1. Download putty from the official site.



2. Run putty. Highlighted in red in the figure note. In the line highlighted in red with the inscription 1, we write the IP address of the Asterisk server. SSH protocol on which putty will work. Never prevent putty from closing if the connection is broken.



3. At the invitation of the system, enter the user (usually this is the root) and press Enter.



4. Enter the password and press Enter.



5. If the authorization was successful, we get something like this screen.



6. Enter: telnet 127.0.0.1 5038 and get the Asterisk prompt: Asterisk Call Manager



7. Enter the following lines:
Action: Login // Press Enter
Username: Which_we_name_to_in_ through_WinSCP // Press Enter
Secret: Password_to_Why_Web_Call_With_With_WinSCP // Press 2 times Enter



Get the message Asterisk: Message: Authentication accepted

8. Enter the following lines:
Action: Logoff // Press 2 times Enter



Get the system message
Response: Goodbye
Message: Thanks for all the fish.

* Here we learned how to connect via putty via SSH to AMI Asterisk


Definition of a mandatory class functional


Let's make the table to understand what functionality needs to be added to a class.
Task.Need.Functional class.
Initial initialization of class variables.Required condition.Constructor __construct ();
The global array is $ this-> ini in which we will store all class variables.
Class closingRequired condition.Destructor __destruct ();
Socket connection.Required condition.Function connect ();
Socket disconnectRequired condition.Function disconnect ();
Authorization in AsterIsk AMI.Required condition.Function init ();
Write to socketRequired condition.Write () function;
Read from socket synchronously.Not a prerequisite.Function read_syn ();
Read from socket asynchronously.Required condition.Read () function;


This is a rough TZ that will allow you to write a class to interact through PHP with Asterisk AMI.

Codding


The rules of writing code in the classroom for clarity when reading it.


When writing this class, I used the following rules:


__construct (), __ destruct () and $ this-> ini



The main task of the constructor is to declare all the variables that will be used in the class. All variables will be stored in the $ this-> ini array.

 <?php class Asterisk_ami { /*   */ public $ini = array(); /*    . */ function __construct () { /*     */ $this->ini["con"] = false; /*    */ } /*   */ function __destruct() { unset ($this->ini); /*           $this->ini       .      php      ,                */ } } ?> 


The rest of the variables in this array, we will add as needed, and we will consider in detail each element of the functionality.

connect () and disconnect ()


To connect to the socket, I decided to use the fsockopen function.

 fsockopen(host, port,$a,$b,$c);  host - IP       , port -       , $a -      , $b -       , $c -  (  ,     ). 


From the description of the function, we can understand that we need to enter at least 2 variables in the global array. This is the IP address of the server and the port for the connection. Since $ a, $ b return values ​​(and we are not a help desk to store references), we will ignore them. And the $ c parameter does not work at all, so we will not keep it in memory.

Why am I writing that does not work? Because this is the real experience of this function in demons, which are
drum on your own PHP specs.

For two new variables, we introduce 2 elements of the array:

 $this->ini["host"] = "127.0.0.1"; /* IP  */ $this->ini["port"] = "5038"; /*    */ 


Write the function:

 <?php class Asterisk_ami { /*   */ public $ini = array(); /*    . */ function __construct () { /*     */ $this->ini["con"] = false; /*    */ $this->ini["host"] = "127.0.0.1"; /* IP  */ $this->ini["port"] = "5038"; /*    */ } /*   */ function __destruct() { unset ($this->ini); /*           $this->ini       .      php      ,                */ } /*     */ function connect() { $this->ini["con"] = fsockopen($this->ini["host"], $this->ini["port"],$a,$b,10); if ($this->ini["con"]) { stream_set_timeout($this->ini["con"], 0, 400000); /*             ,             ,     ,       */ } } /*     */ function connect() { if ($this->ini["con"]) /*    */ { fclose($this->ini["con"]); /*     */ } } } ?> 

write ();


To work with Asterisk AMI, we must write to the socket a packet of strings, in response to which we will also receive strings about the status of our requests. For the record, we will use the php function fwrite. In addition, with each packet we can send the ActionID indicator of our request so that we can receive information packets from Asterisk to classify these packets according to the events sent in advance.
  /*      */ function write($a) /* $a         */ { $m = rand (10000000000000000,99999999900000000); /*  ,    ActionID */ fwrite($this->con, "ActionID: $this->action_id$m\r\n$a\r\n\r\n"); /*    */ $this->sleepi(); /*        . */ return $m; /*  ActionID */ } 


sleepi ();


Every time our script sends a batch of strings to a socket, it returns to execute the following code. Let us see what happens with our package. Our packet is queued for reading by the server controlling the socket. The server, in turn, at the moment may be busy with another operation, and will read our lines a little later. In this regard, in my opinion, it is better to give a little time so that the server has time to read our lines and begin processing our package. For this, I introduced a function to the class that will force the script to fall asleep for a while.
 class Asterisk_ami { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ function __construct () { /*     */ $this->ini["con"] = false; /*    */ $this->ini["host"] = "127.0.0.1"; /* IP  */ $this->ini["port"] = "5038"; /*    */ $this->ini["sleep_time"]=1500; /*      ,    ,  "5" */ } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /*         */ function sleepi () { sleep($this->ini["sleep_time"]); /*       */ } } 

init () and read ();


Everything is simple here. We look code.

  /*     */ function read() { $mm = array(); $b = array(); $k = 0; $s = ""; $this->sleepi(); do { $s.= fread($this->con,1024); sleep($this->read_sleep_time); $mmm=socket_get_status($this->con); } while ($mmm['unread_bytes']); $mm = explode ("\r\n",$s); for ($i=0;$i<count($mm);$i++) { if ($mm[$i]=="") { $k++; } $m = explode(":",$mm[$i]); if (isset($m[1])) { $this->ini["lastRead"][$k][trim($m[0])] = trim($m[1]); } } return $this->ini["lastRead"]; } 


As a result, we got a class for working with Asterisk AMI:

 <?php class Asterisk_ami { public $ini = array(); function __construct () { $this->ini["con"] = false; $this->ini["host"] = "127.0.0.1"; $this->ini["port"] = "5038"; $this->ini["lastActionID"] = 0; $this->ini["lastRead"] = array (); $this->ini["sleep_time"]=1.5; $this->ini["login"] = "admin"; $this->ini["password"] = "amp111"; } function __destruct() { unset ($this->ini); } public function connect() { $this->ini["con"] = fsockopen($this->ini["host"], $this->ini["port"],$a,$b,10); if ($this->ini["con"]) { stream_set_timeout($this->ini["con"], 0, 400000); } } public function disconnect() { if ($this->ini["con"]) { fclose($this->ini["con"]); } } public function write($a) { $this->ini["lastActionID"] = rand (10000000000000000,99999999900000000); fwrite($this->ini["con"], "ActionID: ".$this->ini["lastActionID"]."\r\n$a\r\n\r\n"); $this->sleepi(); return $this->ini["lastActionID"]; } public function sleepi () { sleep($this->ini["sleep_time"]); } public function read() { $mm = array(); $b = array(); $k = 0; $s = ""; $this->sleepi(); do { $s.= fread($this->ini["con"],1024); sleep(0.005); $mmm=socket_get_status($this->ini["con"]); } while ($mmm['unread_bytes']); $mm = explode ("\r\n",$s); $this->ini["lastRead"] = array(); for ($i=0;$i<count($mm);$i++) { if ($mm[$i]=="") { $k++; } $m = explode(":",$mm[$i]); if (isset($m[1])) { $this->ini["lastRead"][$k][trim($m[0])] = trim($m[1]); } } unset ($b); unset ($k); unset ($mm); unset ($mm); unset ($mmm); unset ($i); unset ($s); return $this->ini["lastRead"]; } public function init() { return $this->write("Action: Login\r\nUsername: ".$this->ini["login"]."\r\nSecret: ".$this->ini["login"]."\r\n\r\n"); } } $a = new Asterisk_ami(); $a->connect(); if ($a->ini["con"]) { $a->init(); $a->write("Action: ListCommands"); var_dump($a->read()); $a->disconnect(); } unset($a); ?> 


Script result
array (4) {
[0] =>
array (2) {
["Response"] =>
string (7) "Success"
["Message"] =>
string (23) "Authentication accepted"
}
[1] =>
array (3) {
["Event"] =>
string (11) "FullyBooted"
["Privilege"] =>
string (10) "system, all"
["Status"] =>
string (12) "Fully Booted"
}
[2] =>
array (2) {
["Response"] =>
string (7) "Success"
["Events"] =>
string (2) "On"
}
[3] =>
array (115) {
["Response"] =>
string (7) "Success"
["ActionID"] =>
string (17) "67932087789661188"
["WaitEvent"] =>
string (34) “Wait for an event to occur. (Priv »
["DeviceStateList"] =>
string (44) “List the current known device states. (Priv »
["PresenceStateList"] =>
string (46) “List the current known presence states. (Priv »
["QueueReset"] =>
string (30) “Reset queue statistics. (Priv »
["QueueReload"] =>
string (71) “Reload a queue, queues, or any sub-section of a queue or queues. (Priv »
["QueueRule"] =>
string (19) “Queue Rules. (Priv »
["QueueMemberRingInUse"] =>
string (50) “Set the ringinuse value for a queue member. (Priv »
["QueuePenalty"] =>
string (42) “Set the penalty for a queue member. (Priv »
["QueueLog"] =>
string (38) “Adds custom entry in queue_log. (Priv »
["QueuePause"] =>
string (52) “Makes a queue member temporarily unavailable. (Priv »
["QueueRemove"] =>
string (35) “Remove interface from queue. (Priv »
["QueueAdd"] =>
string (30) “Add interface to queue. (Priv »
["QueueSummary"] =>
string (26) “Show queue summary. (Priv »
["QueueStatus"] =>
string (25) “Show queue status. (Priv »
["Queues"] =>
string (14) "Queues. (Priv »
["ControlPlayback"] =>
string (64) "Control the playback of a file being played to a channel. (Priv »
["StopMixMonitor"] =>
string (86) "Stop the recording of a file handle. (Priv »
["MixMonitor"] =>
string (178) “Record a call and mix the audio during the recording. The audio file is available for processing during the dialplan execution. (Priv »
["MixMonitorMute"] =>
string (44) “Mute / unMute a Mixmonitor recording. (Priv »
["VoicemailRefresh"] =>
string (51) "Tell Asterisk to poll mailboxes for a change (Priv"
["VoicemailUsersList"] =>
string (43) "List All Voicemail User Information. (Priv »
["PlayDTMF"] =>
string (46) “Play DTMF signal on a specific channel. (Priv »
["MuteAudio"] =>
string (28) “Mute an audio stream. (Priv »
["ConfbridgeSetSingleVideoSrc"] =>
string (94) "distributed to all other participants. (Priv »
["ConfbridgeStopRecord"] =>
string (46) “Stop recording a Confbridge conference. (Priv »
["ConfbridgeStartRecord"] =>
string (47) “Start recording a Confbridge conference. (Priv »
["Confbridge Lock"] =>
string (36) “Lock a Confbridge conference. (Priv »
["ConfbridgeUnlock"] =>
string (38) “Unlock a Confbridge conference. (Priv »
["ConfbridgeKick"] =>
string (30) “Kick a Confbridge user. (Priv »
["ConfbridgeUnmute"] =>
string (32) “Unmute a Confbridge user. (Priv »
["ConfbridgeMute"] =>
string (30) “Mute a Confbridge user. (Priv »
["ConfbridgeListRooms"] =>
string (31) “List active conferences. (Priv »
["ConfbridgeList"] =>
string (41) "List participants in a conference. (Priv »
["MeetmeListRooms"] =>
string (31) “List active conferences. (Priv »
["MeetmeList"] =>
string (41) "List participants in a conference. (Priv »
[“MeetmeUnmute”] =>
string (28) “Unmute a Meetme user. (Priv »
[“MeetmeMute”] =>
string (26) “Mute a Meetme user. (Priv »
["PJSIPNotify"] =>
string (63) “Send a message to either endpoint or an arbitrary URI. (Priv »
["PJSIPShowRegistrationsOutbound"] =>
string (42) “Lists PJSIP outbound registrations. (Priv »
["PJSIPUnregister"] =>
string (43) “Unregister an outbound registration. (Priv »
["PJSIPShowRegistrationsInbound"] =>
string (41) "Lists PJSIP inbound registrations. (Priv »
["PRIDebugFileUnset"] =>
string (50) "Disables file output for PRI debug messages (Priv"
["PRIDebugFileSet"] ​​=>
string (53) "Set the file used for PRI debug message output (Priv"
["PRIDebugSet"] ​​=>
string (38) "Set PRI debug levels for a span (Priv"
["PRIShowSpans"] =>
string (32) “Show status of PRI spans. (Priv »
["DAHDIRestart"] =>
string (55) “Fully Restart DAHDI channels (terminates calls). (Priv »
["DAHDIShowChannels"] =>
string (37) “Show status of DAHDI channels. (Priv »
["DAHDIDNDoff"] =>
string (54) “Toggle DAHDI channel Do Not Disturb status OFF. (Priv »
["DAHDIDNDon"] =>
string (53) "Toggle DAHDI channel Do Not Disturb status ON. (Priv »
["DAHDIDialOffhook"] =>
string (45) “Dial over DAHDI channel while offhook. (Priv »
["DAHDIHangup"] =>
string (28) “Hangup DAHDI Channel. (Priv »
["DAHDITransfer"] =>
string (30) “Transfer DAHDI Channel. (Priv »
["SIPpeerstatus"] =>
string (54) “Show the status of one of all sip peers. (Priv »
["SIPnotify"] =>
string (25) “Send a SIP notify. (Priv »
["SIPshowregistry"] =>
string (44) “Show SIP registrations (text format). (Priv »
["SIPqualifypeer"] =>
string (25) “Qualify SIP peers. (Priv »
["SIPshowpeer"] =>
string (35) “show SIP peer (text format). (Priv »
["SIPpeers"] =>
string (36) “List SIP peers (text format). (Priv »
["IAXregistry"] =>
string (30) “Show IAX registrations. (Priv »
["IAXnetstats"] =>
string (25) “Show IAX Netstats. (Priv »
["IAXpeerlist"] =>
string (22) “List IAX Peers. (Priv »
["IAXpeers"] =>
string (22) “List IAX peers. (Priv »
["Park"] =>
string (22) “Park a channel. (Priv »
["ParkedCalls"] =>
string (25) “List parked calls. (Priv »
["Parkinglots"] =>
string (33) "Get a list of parking lots (Priv"
["FAXStats"] =>
string (35) "Responds with fax statistics (Priv"
["FAXSession"] =>
string (67) "Responds with a detailed description of a single FAX session (Priv"
["FAXSessions"] =>
string (32) "Lists active FAX sessions (Priv"
["PJSIPShowResourceLists"] =>
string (55) "Displays settings for configured resource lists. (Priv »
["PJSIPShowSubscriptionsOutbound"] =>
string (27) “Lists subscriptions. (Priv »
["PJSIPShowSubscriptionsInbound"] =>
string (27) “Lists subscriptions. (Priv »
["UnpauseMonitor"] =>
string (39) “Unpause monitoring of a channel. (Priv »
["PauseMonitor"] =>
string (37) “Pause monitoring of a channel. (Priv »
["ChangeMonitor"] =>
string (47) “Change monitoring filename of a channel. (Priv »
["StopMonitor"] =>
string (33) "Stop monitoring a channel. (Priv »
["Monitor"] =>
string (25) “Monitor a channel. (Priv »
["PJSIPQualify"] =>
string (37) “Qualify a chan_pjsip endpoint. (Priv »
["PJSIPShowEndpoint"] =>
string (53) “Detail of the endpoint and its objects. (Priv »
["PJSIPShowEndpoints"] =>
string (29) “Lists PJSIP endpoints. (Priv »
["BridgeKick"] =>
string (36) “Kick a channel from a bridge. (Priv »
["BridgeDestroy"] =>
string (24) “Destroy a bridge. (Priv »
["BridgeInfo"] =>
string (38) "Get information about a bridge. (Priv »
["BridgeList"] =>
string (43) "Get a list of bridges in the system." (Priv »
["BlindTransfer"] =>
string (57) "Blind transfer channel (s) to the given destination (Priv"
["Filter"] =>
string (63) "Dynamically add filters for the current manager session. (Priv »
["ModuleCheck"] =>
string (33) "Check if module is loaded. (Priv »
["ModuleLoad"] =>
string (25) “Module management. (Priv »
["CoreShowChannels"] =>
string (38) "List currently active channels. (Priv »
["LoggerRotate"] =>
string (45) "Reload and rotate the Asterisk logger. (Priv »
["Reload"] =>
string (27) “Send a reload event. (Priv »
["CoreStatus"] =>
string (38) “Show PBX core status variables. (Priv »
["CoreSettings"] =>
string (44) “Show PBX core settings (version etc). (Priv »
["UserEvent"] =>
string (31) “Send an arbitrary event. (Priv »
["UpdateConfig"] =>
string (34) “Update basic configuration. (Priv »
["SendText"] =>
string (36) “Send text message to channel. (Priv »
["ListCommands"] =>
string (39) "List available manager commands. (Priv »
["MailboxCount"] =>
string (35) "Check Mailbox Message Count. (Priv »
["MailboxStatus"] =>
string (21) “Check mailbox. (Priv »
["AbsoluteTimeout"] =>
string (28) “Set absolute timeout. (Priv »
["PresenceState"] =>
string (27) "Check Presence State (Priv"
["ExtensionState"] =>
string (30) “Check Extension Status. (Priv »
["Command"] =>
string (36) “Execute Asterisk CLI Command. (Priv »
["Originate"] =>
string (24) “Originate a call. (Priv »
["Atxfer"] =>
string (25) "Attended transfer. (Priv »
["Redirect"] =>
string (34) “Redirect (transfer) a call. (Priv »
["ListCategories"] =>
string (45) “List categories in configuration file. (Priv »
["CreateConfig"] =>
string (60) "Creates an empty file in the configuration directory. (Priv »
["Status"] =>
string (27) “List channel status. (Priv »
["GetConfigJSON"] =>
string (44) “Retrieve configuration (JSON format). (Priv »
["GetConfig"] =>
string (30) “Retrieve configuration. (Priv »
["Getvar"] =>
string (49) “Gets a channel variable or function value. (Priv »
["Setvar"] =>
string (49) “Sets a channel variable or function value. (Priv »
["ShowDialPlan"] =>
string (22) "Show dialplan contexts"
}
}


Summary


Creating your own class for working with sockets in asynchronous mode is not difficult. And it does not matter whether it will work with Asterisk AMI or another server application.

Special thanks to HiNeX for errors found in the intermediate code.

Changed the finished class in connection with the recommendations of AotD and RUgaleFF .

Thanks to everyone who read it. And see you on the TM portals.

(If you think it is worth continuing, which of the AMER Asterisk functionaries do you want me to show in the next part? Write in the comments.)

We kindly request for those who minus the article, if it is not difficult for you, please write the reason.

Source: https://habr.com/ru/post/253387/


All Articles