Pull to refresh

Пример реализации обращения к Asterisk CLI на PHP. Структуризация ответа звездочки

Reading time 19 min
Views 17K
В сети распространено заблуждение, что дружба PHP и Asterisk CLI — это костыль. Возможно, это и так, но иногда по требованию заказчика для интеграции, например, с CRM системой приходится связывать с VOIP, и чаще всего это Asterisk.

Как же быстро и просто подвязать несколько команд из CLI, да так, чтобы можно было получать уже готовые массивы данных для передачи в веб-приложение?

На самом деле все очень просто. Немного рутиной работы, немного логики и, вуаля.

Для получения данных мы будем использовать документированную возможность Asterisk обращение к CLI из-вне.

Читаем матчасть:

asterisk -rx вызов команды CLI извне.
-r: Соединяемся с запущенным в фоне сервером Asterisk, и получаем доступ к CLI консоли
-x: В комбинации с параметром -r, выполняет команду CLI Asterisk

Пример: asterisk -rx «sip reload» — произведет рестарт sip.

Запрет цветного вывода в консоли.
У звездочки есть еще один полезный ключ:
-n: Запретить поддержку цветного вывода ANSI

Но как я не пытался отключить цветной вывод, это у меня не получилось, видать руки кривоватые.

Для запуска команды мы будем использовать две функции PHP, exec() и passthru().

<?php
function asterisk_cli_exec ($a)
    {
        exec("asterisk -rx '$a'", $b);
        return $b;
    }
    
function asterisk_cli_passthru ($a)
    {
        passthru("asterisk -rx '$a'", $b);
        return $b;
    }       
?>                      


Результат выполнения var_dump(asterisk_cli_exec(core show help manager)).
array(11) {
[0]=>
string(63) «manager reload — Reload manager configurations»
[1]=>
string(85) «manager set debug [on|off] — Show, enable, disable debugging of the manager code»
[2]=>
string(66) «manager show command — Show a manager interface command»
[3]=>
string(65) «manager show commands — List manager interface commands»
[4]=>
string(72) «manager show connected — List connected manager interface users»
[5]=>
string(70) «manager show eventq — List manager interface queued events»
[6]=>
string(63) «manager show events — List manager interface events»
[7]=>
string(64) «manager show event — Show a manager interface event»
[8]=>
string(62) «manager show settings — Show manager global settings»
[9]=>
string(63) «manager show users — List configured manager users»
[10]=>
string(80) «manager show user — Display information on a specific manager user»
}

Результат выполнения asterisk_cli_passthru(core show help manager).
manager reload — Reload manager configurations
manager set debug [on|off] — Show, enable, disable debugging of the manager code
manager show command — Show a manager interface command
manager show commands — List manager interface commands
manager show connected — List connected manager interface users
manager show eventq — List manager interface queued events
manager show events — List manager interface events
manager show event — Show a manager interface event
manager show settings — Show manager global settings
manager show users — List configured manager users
manager show user — Display information on a specific manager user


В итоге мы получили две функции, которые дают нам 2 различных варианта для выдачи информации:

exec() — в виде массива строк;
passthru() — прямой вывод результата.

Функция asterisk_cli_passthru() может пригодиться только для создания собственного CLI интерфейса из браузера. В основном прямой вывод не требуется. Для web приложений нужны массивы. На данном этапе мы сразу же забудем про эту «вкусняшку», так как она уже полностью реализована, и более для нее ничего не требуется.

Итак перейдем к самому интересному.

Следующим шагом мы разделим команды на «простые» и «сложные». Простые команды это те которые в своем выводе не выдают ассоциативный массив данных.

Простые команды
manager reload
array(0) {
}

manager set debug
array(1) {
[0]=>
string(20) «manager debug is off»
}

manager show command WaitEvent
[Syntax]
Action: WaitEvent
[ActionID:] <value>
Timeout: <value>

[Synopsis]
Wait for an event to occur.

[Description]
This action will ellicit a 'Success' response. Whenever a manager event is
queued. Once WaitEvent has been called on an HTTP manager session, events
will be generated and queued.

[Arguments]
ActionID
    ActionID for this transaction. Will be returned.
Timeout
    Maximum time (in seconds) to wait for events, '-1' means forever

manager show event Status
Event: Status
[Synopsis]
Raised in response to a Status command.

[Syntax]
Event: Status
[ActionID:] <value>
Type: <value>
DNID: <value>
TimeToHangup: <value>
BridgeID: <value>
Linkedid: <value>
Application: <value>
Data: <value>
Nativeformats: <value>
Readformat: <value>
Readtrans: <value>
Writeformat: <value>
Writetrans: <value>
Callgroup: <value>
Pickupgroup: <value>
Seconds: <value>



Сложные команды
manager show commands
array(137) {
  [0]=>
  string(42) "  Action                          Synopsis"
  [1]=>
  string(42) "  ------                          --------"
  [2]=>
  string(61) "  WaitEvent                       Wait for an event to occur."
  [3]=>
  string(71) "  DeviceStateList                 List the current known device states."
  [4]=>
  string(73) "  PresenceStateList               List the current known presence states."
  [5]=>
  string(57) "  QueueReset                      Reset queue statistics."
  [6]=>
  string(79) "  QueueReload                     Reload a queue, queues, or any sub-section of"
  [7]=>
  string(46) "  QueueRule                       Queue Rules."
  [8]=>
  string(77) "  QueueMemberRingInUse            Set the ringinuse value for a queue member."
  [9]=>
  string(69) "  QueuePenalty                    Set the penalty for a queue member."
  [10]=>
  string(65) "  QueueLog                        Adds custom entry in queue_log."
  [11]=>
  string(79) "  QueuePause                      Makes a queue member temporarily unavailable."
  [12]=>
  string(62) "  QueueRemove                     Remove interface from queue."
  [13]=>
  string(57) "  QueueAdd                        Add interface to queue."
  [14]=>
  string(53) "  QueueSummary                    Show queue summary."
  [15]=>
  string(52) "  QueueStatus                     Show queue status."
  [16]=>
  string(41) "  Queues                          Queues."
  [17]=>
  string(80) "  ControlPlayback                 Control the playback of a file being played to"
  [18]=>
  string(79) "  StopMixMonitor                  Stop recording a call through MixMonitor, and"
  [19]=>
  string(80) "  MixMonitor                      Record a call and mix the audio during the rec"
  [20]=>
  string(71) "  MixMonitorMute                  Mute / unMute a Mixmonitor recording."
  [21]=>
  string(78) "  VoicemailRefresh                Tell Asterisk to poll mailboxes for a change"
  [22]=>
  string(70) "  VoicemailUsersList              List All Voicemail User Information."
  [23]=>
  string(73) "  PlayDTMF                        Play DTMF signal on a specific channel."
  [24]=>
  string(55) "  MuteAudio                       Mute an audio stream."
  [25]=>
  string(80) "  ConfbridgeSetSingleVideoSrc     Set a conference user as the single video sour"
  [26]=>
  string(73) "  ConfbridgeStopRecord            Stop recording a Confbridge conference."
  [27]=>
  string(74) "  ConfbridgeStartRecord           Start recording a Confbridge conference."
  [28]=>
  string(63) "  ConfbridgeLock                  Lock a Confbridge conference."
  [29]=>
  string(65) "  ConfbridgeUnlock                Unlock a Confbridge conference."
  [30]=>
  string(57) "  ConfbridgeKick                  Kick a Confbridge user."
  [31]=>
  string(59) "  ConfbridgeUnmute                Unmute a Confbridge user."
  [32]=>
  string(57) "  ConfbridgeMute                  Mute a Confbridge user."
  [33]=>
  string(58) "  ConfbridgeListRooms             List active conferences."
  [34]=>
  string(68) "  ConfbridgeList                  List participants in a conference."
  [35]=>
  string(58) "  MeetmeListRooms                 List active conferences."
  [36]=>
  string(68) "  MeetmeList                      List participants in a conference."
  [37]=>
  string(55) "  MeetmeUnmute                    Unmute a Meetme user."
  [38]=>
  string(53) "  MeetmeMute                      Mute a Meetme user."
  [39]=>
  string(80) "  PJSIPNotify                     Send a NOTIFY to either an endpoint or an arbi"
  [40]=>
  string(69) "  PJSIPShowRegistrationsOutbound  Lists PJSIP outbound registrations."
  [41]=>
  string(70) "  PJSIPUnregister                 Unregister an outbound registration."
  [42]=>
  string(68) "  PJSIPShowRegistrationsInbound   Lists PJSIP inbound registrations."
  [43]=>
  string(77) "  PRIDebugFileUnset               Disables file output for PRI debug messages"
  [44]=>
  string(80) "  PRIDebugFileSet                 Set the file used for PRI debug message output"
  [45]=>
  string(65) "  PRIDebugSet                     Set PRI debug levels for a span"
  [46]=>
  string(59) "  PRIShowSpans                    Show status of PRI spans."
  [47]=>
  string(80) "  DAHDIRestart                    Fully Restart DAHDI channels (terminates calls"
  [48]=>
  string(64) "  DAHDIShowChannels               Show status of DAHDI channels."
  [49]=>
  string(80) "  DAHDIDNDoff                     Toggle DAHDI channel Do Not Disturb status OFF"
  [50]=>
  string(80) "  DAHDIDNDon                      Toggle DAHDI channel Do Not Disturb status ON."
  [51]=>
  string(72) "  DAHDIDialOffhook                Dial over DAHDI channel while offhook."
  [52]=>
  string(55) "  DAHDIHangup                     Hangup DAHDI Channel."
  [53]=>
  string(57) "  DAHDITransfer                   Transfer DAHDI Channel."
  [54]=>
  string(80) "  SIPpeerstatus                   Show the status of one or all of the sip peers"
  [55]=>
  string(52) "  SIPnotify                       Send a SIP notify."
  [56]=>
  string(71) "  SIPshowregistry                 Show SIP registrations (text format)."
  [57]=>
  string(52) "  SIPqualifypeer                  Qualify SIP peers."
  [58]=>
  string(62) "  SIPshowpeer                     show SIP peer (text format)."
  [59]=>
  string(63) "  SIPpeers                        List SIP peers (text format)."
  [60]=>
  string(57) "  IAXregistry                     Show IAX registrations."
  [61]=>
  string(52) "  IAXnetstats                     Show IAX Netstats."
  [62]=>
  string(49) "  IAXpeerlist                     List IAX Peers."
  [63]=>
  string(49) "  IAXpeers                        List IAX peers."
  [64]=>
  string(49) "  Park                            Park a channel."
  [65]=>
  string(52) "  ParkedCalls                     List parked calls."
  [66]=>
  string(60) "  Parkinglots                     Get a list of parking lots"
  [67]=>
  string(77) "  AGI                             Add an AGI command to execute by Async AGI."
  [68]=>
  string(62) "  FAXStats                        Responds with fax statistics"
  [69]=>
  string(80) "  FAXSession                      Responds with a detailed description of a sing"
  [70]=>
  string(59) "  FAXSessions                     Lists active FAX sessions"
  [71]=>
  string(80) "  PJSIPShowResourceLists          Displays settings for configured resource list"
  [72]=>
  string(54) "  PJSIPShowSubscriptionsOutbound  Lists subscriptions."
  [73]=>
  string(54) "  PJSIPShowSubscriptionsInbound   Lists subscriptions."
  [74]=>
  string(66) "  UnpauseMonitor                  Unpause monitoring of a channel."
  [75]=>
  string(64) "  PauseMonitor                    Pause monitoring of a channel."
  [76]=>
  string(74) "  ChangeMonitor                   Change monitoring filename of a channel."
  [77]=>
  string(60) "  StopMonitor                     Stop monitoring a channel."
  [78]=>
  string(52) "  Monitor                         Monitor a channel."
  [79]=>
  string(64) "  PJSIPQualify                    Qualify a chan_pjsip endpoint."
  [80]=>
  string(80) "  PJSIPShowEndpoint               Detail listing of an endpoint and its objects."
  [81]=>
  string(56) "  PJSIPShowEndpoints              Lists PJSIP endpoints."
  [82]=>
  string(63) "  BridgeKick                      Kick a channel from a bridge."
  [83]=>
  string(51) "  BridgeDestroy                   Destroy a bridge."
  [84]=>
  string(65) "  BridgeInfo                      Get information about a bridge."
  [85]=>
  string(70) "  BridgeList                      Get a list of bridges in the system."
  [86]=>
  string(80) "  BlindTransfer                   Blind transfer channel(s) to the given destina"
  [87]=>
  string(80) "  Filter                          Dynamically add filters for the current manage"
  [88]=>
  string(80) "  AOCMessage                      Generate an Advice of Charge message on a chan"
  [89]=>
  string(60) "  ModuleCheck                     Check if module is loaded."
  [90]=>
  string(52) "  ModuleLoad                      Module management."
  [91]=>
  string(65) "  CoreShowChannels                List currently active channels."
  [92]=>
  string(72) "  LoggerRotate                    Reload and rotate the Asterisk logger."
  [93]=>
  string(54) "  Reload                          Send a reload event."
  [94]=>
  string(65) "  CoreStatus                      Show PBX core status variables."
  [95]=>
  string(71) "  CoreSettings                    Show PBX core settings (version etc)."
  [96]=>
  string(58) "  UserEvent                       Send an arbitrary event."
  [97]=>
  string(61) "  UpdateConfig                    Update basic configuration."
  [98]=>
  string(63) "  SendText                        Send text message to channel."
  [99]=>
  string(66) "  ListCommands                    List available manager commands."
  [100]=>
  string(62) "  MailboxCount                    Check Mailbox Message Count."
  [101]=>
  string(48) "  MailboxStatus                   Check mailbox."
  [102]=>
  string(55) "  AbsoluteTimeout                 Set absolute timeout."
  [103]=>
  string(54) "  PresenceState                   Check Presence State"
  [104]=>
  string(57) "  ExtensionState                  Check Extension Status."
  [105]=>
  string(63) "  Command                         Execute Asterisk CLI Command."
  [106]=>
  string(51) "  Originate                       Originate a call."
  [107]=>
  string(52) "  Atxfer                          Attended transfer."
  [108]=>
  string(61) "  Redirect                        Redirect (transfer) a call."
  [109]=>
  string(72) "  ListCategories                  List categories in configuration file."
  [110]=>
  string(80) "  CreateConfig                    Creates an empty file in the configuration dir"
  [111]=>
  string(54) "  Status                          List channel status."
  [112]=>
  string(71) "  GetConfigJSON                   Retrieve configuration (JSON format)."
  [113]=>
  string(57) "  GetConfig                       Retrieve configuration."
  [114]=>
  string(76) "  Getvar                          Gets a channel variable or function value."
  [115]=>
  string(76) "  Setvar                          Sets a channel variable or function value."
  [116]=>
  string(71) "  ShowDialPlan                    Show dialplan contexts and extensions"
  [117]=>
  string(49) "  Hangup                          Hangup channel."
  [118]=>
  string(66) "  Challenge                       Generate Challenge for MD5 Auth."
  [119]=>
  string(48) "  Login                           Login Manager."
  [120]=>
  string(49) "  Logoff                          Logoff Manager."
  [121]=>
  string(53) "  Events                          Control Event Flow."
  [122]=>
  string(52) "  Ping                            Keepalive command."
  [123]=>
  string(78) "  LocalOptimizeAway               Optimize away a local channel when possible."
  [124]=>
  string(74) "  ExtensionStateList              List the current known extension states."
  [125]=>
  string(77) "  MessageSend                     Send an out of call message to an endpoint."
  [126]=>
  string(73) "  Bridge                          Bridge two channels already in the PBX."
  [127]=>
  string(71) "  DialplanExtensionRemove         Remove an extension from the dialplan"
  [128]=>
  string(66) "  DialplanExtensionAdd            Add an extension to the dialplan"
  [129]=>
  string(66) "  BridgeTechnologyUnsuspend       Unsuspend a bridging technology."
  [130]=>
  string(64) "  BridgeTechnologySuspend         Suspend a bridging technology."
  [131]=>
  string(80) "  BridgeTechnologyList            List available bridging technologies and their"
  [132]=>
  string(61) "  DataGet                         Retrieve the data api tree."
  [133]=>
  string(47) "  DBPut                           Put DB entry."
  [134]=>
  string(49) "  DBDelTree                       Delete DB Tree."
  [135]=>
  string(50) "  DBDel                           Delete DB entry."
  [136]=>
  string(47) "  DBGet                           Get DB Entry."
}


manager show connected
array(3) {
  [0]=>
  string(132) "  Username         IP Address                                               Start       Elapsed     FileDes   HttpCnt   Read   Write"
  [1]=>
  string(142) "  cxpanel          127.0.0.1                                                1426370041  5167        19        0         2147483647  2147483647"
  [2]=>
  string(18) "1 users connected."
}


manager show eventq
array(16) {
  [0]=>
  string(11) "Usecount: 1"
  [1]=>
  string(16) "Category: 262144"
  [2]=>
  string(6) "Event:"
  [3]=>
  string(21) "Event: SuccessfulAuth"
  [4]=>
  string(23) "Privilege: security,all"
  [5]=>
  string(37) "EventTV: 2015-03-15T05:22:02.054+0600"
  [6]=>
  string(23) "Severity: Informational"
  [7]=>
  string(12) "Service: AMI"
  [8]=>
  string(15) "EventVersion: 1"
  [9]=>
  string(16) "AccountID: admin"
  [10]=>
  string(25) "SessionID: 0x7ff0f0000ad8"
  [11]=>
  string(35) "LocalAddress: IPV4/TCP/0.0.0.0/5038"
  [12]=>
  string(39) "RemoteAddress: IPV4/TCP/127.0.0.1/39453"
  [13]=>
  string(16) "UsingPassword: 0"
  [14]=>
  string(39) "SessionTV: 2015-03-15T05:22:02.054+0600"
  [15]=>
  string(0) ""
}


manager show events
array(49) {
  [0]=>
  string(7) "Events:"
  [1]=>
  string(66) "  --------------------  --------------------  --------------------"
  [2]=>
  string(51) "  AGIExecEnd            AGIExecStart          AOC-D"
  [3]=>
  string(57) "  AOC-E                 AOC-S                 AgentCalled"
  [4]=>
  string(55) "  AgentComplete         AgentConnect          AgentDump"
  [5]=>
  string(63) "  AgentLogin            AgentLogoff           AgentRingNoAnswer"
  [6]=>
  string(51) "  Agents                AgentsComplete        Alarm"
  [7]=>
  string(57) "  AlarmClear            AorDetail             AsyncAGIEnd"
  [8]=>
  string(62) "  AsyncAGIExec          AsyncAGIStart         AttendedTransfer"
  [9]=>
  string(59) "  AuthDetail            AuthMethodNotAllowed  BlindTransfer"
  [10]=>
  string(57) "  BridgeCreate          BridgeDestroy         BridgeEnter"
  [11]=>
  string(59) "  BridgeLeave           ChallengeResponseFai  ChallengeSent"
  [12]=>
  string(65) "  ChanSpyStart          ChanSpyStop           ChannelTalkingStart"
  [13]=>
  string(60) "  ChannelTalkingStop    ConfbridgeEnd         ConfbridgeJoin"
  [14]=>
  string(62) "  ConfbridgeLeave       ConfbridgeMute        ConfbridgeRecord"
  [15]=>
  string(63) "  ConfbridgeStart       ConfbridgeStopRecord  ConfbridgeTalking"
  [16]=>
  string(61) "  ConfbridgeUnmute      ContactStatusDetail   CoreShowChannel"
  [17]=>
  string(54) "  CoreShowChannelsComp  DAHDIChannel          DNDState"
  [18]=>
  string(53) "  DeviceStateChange     DialBegin             DialEnd"
  [19]=>
  string(61) "  EndpointDetail        EndpointList          ExtensionStatus"
  [20]=>
  string(62) "  FAXSession            FAXSessionsComplete   FAXSessionsEntry"
  [21]=>
  string(55) "  FAXStats              FAXStatus             FailedACL"
  [22]=>
  string(62) "  FullyBooted           Hangup                HangupHandlerPop"
  [23]=>
  string(59) "  HangupHandlerPush     HangupHandlerRun      HangupRequest"
  [24]=>
  string(62) "  Hold                  IdentifyDetail        InvalidAccountID"
  [25]=>
  string(62) "  InvalidPassword       InvalidTransport      LoadAverageLimit"
  [26]=>
  string(66) "  LocalBridge           LocalOptimizationBeg  LocalOptimizationEnd"
  [27]=>
  string(60) "  MCID                  MWIGet                MWIGetComplete"
  [28]=>
  string(57) "  MeetmeEnd             MeetmeJoin            MeetmeLeave"
  [29]=>
  string(59) "  MeetmeMute            MeetmeTalkRequest     MeetmeTalking"
  [30]=>
  string(58) "  MemoryLimit           MiniVoiceMail         MonitorStart"
  [31]=>
  string(61) "  MonitorStop           MusicOnHoldStart      MusicOnHoldStop"
  [32]=>
  string(54) "  NewAccountCode        NewCallerid           NewExten"
  [33]=>
  string(63) "  Newchannel            Newstate              OriginateResponse"
  [34]=>
  string(63) "  ParkedCall            ParkedCallGiveUp      ParkedCallTimeOut"
  [35]=>
  string(65) "  PeerStatus            Pickup                PresenceStateChange"
  [36]=>
  string(61) "  PresenceStatus        QueueCallerAbandon    QueueCallerJoin"
  [37]=>
  string(62) "  QueueCallerLeave      QueueMemberAdded      QueueMemberPause"
  [38]=>
  string(66) "  QueueMemberPenalty    QueueMemberRemoved    QueueMemberRinginuse"
  [39]=>
  string(54) "  QueueMemberStatus     RTCPReceived          RTCPSent"
  [40]=>
  string(52) "  ReceiveFAX            Registry              Reload"
  [41]=>
  string(65) "  RequestBadFormat      RequestNotAllowed     RequestNotSupported"
  [42]=>
  string(58) "  SIPQualifyPeerDone    SendFAX               SessionLimit"
  [43]=>
  string(63) "  SessionTimeout        Shutdown              SoftHangupRequest"
  [44]=>
  string(52) "  SpanAlarm             SpanAlarmClear        Status"
  [45]=>
  string(61) "  StatusComplete        SuccessfulAuth        TransportDetail"
  [46]=>
  string(52) "  UnParkedCall          UnexpectedAddress     Unhold"
  [47]=>
  string(52) "  UserEvent             VarSet                VarSet"
  [48]=>
  string(8) "  VarSet"
}
 


manager show settings

array(17) {
  [0]=>
  string(0) ""
  [1]=>
  string(16) "Global Settings:"
  [2]=>
  string(16) "----------------"
  [3]=>
  string(32) "  Manager (AMI):             Yes"
  [4]=>
  string(31) "  Web Manager (AMI/HTTP):    No"
  [5]=>
  string(41) "  TCP Bindaddress:           0.0.0.0:5038"
  [6]=>
  string(31) "  HTTP Timeout (minutes):    60"
  [7]=>
  string(31) "  TLS Enable:                No"
  [8]=>
  string(37) "  TLS Bindaddress:           Disabled"
  [9]=>
  string(41) "  TLS Certfile:              asterisk.pem"
  [10]=>
  string(17) "  TLS Privatekey:"
  [11]=>
  string(13) "  TLS Cipher:"
  [12]=>
  string(32) "  Allow multiple login:      Yes"
  [13]=>
  string(32) "  Display connects:          Yes"
  [14]=>
  string(31) "  Timestamp events:          No"
  [15]=>
  string(15) "  Channel vars:"
  [16]=>
  string(31) "  Debug:                     No"
}
 


manager show users

array(8) {
  [0]=>
  string(0) ""
  [1]=>
  string(8) "username"
  [2]=>
  string(8) "--------"
  [3]=>
  string(5) "rinat"
  [4]=>
  string(5) "admin"
  [5]=>
  string(7) "cxpanel"
  [6]=>
  string(19) "-------------------"
  [7]=>
  string(27) "3 manager users configured."
}
 


manager show user admin
array(9) {
  [0]=>
  string(0) ""
  [1]=>
  string(25) "          username: admin"
  [2]=>
  string(25) "            secret: <Set>"
  [3]=>
  string(23) "               ACL: yes"
  [4]=>
  string(115) "         read perm: system,call,log,verbose,command,agent,user,config,dtmf,reporting,cdr,dialplan,originate,message"
  [5]=>
  string(115) "        write perm: system,call,log,verbose,command,agent,user,config,dtmf,reporting,cdr,dialplan,originate,message"
  [6]=>
  string(23) "   displayconnects: yes"
  [7]=>
  string(23) "allowmultiplelogin: yes"
  [8]=>
  string(19) "         Variables:"
}
 




Теперь начнем разбор полетов.

Простые функции у нас не будут обрабатываться, отдадим их как есть нашему клиенту.

Начнем коддинг.

<?php

function asterisk_cli_exec ($a)
    {
        exec("asterisk -rx '$a'", $b);
        return $b;
    }
    
function asterisk_cli_passthru ($a)
    {
        passthru("asterisk -rx '$a'", $b);
        return $b;
    }
    
function cli ($a)
    {
    /*
    Матчасть:
    trim - удаляет пробелы в начале и конце статьи
    strtolower - преобразует строку в нижний регистр
    str_replace - удаляет двойные пробелы 
    */
    $a=str_replace("  ", " ",trim(strtolower($a)));
    /* переменую $b мы будем использовать в кострукции switch */
    $b=-1;
    /* Массив в котором мы будем хранить значения полученные из Астериска */
    $result = array ();
    /* массив где мы храним cli команды */
    $ar = array ("manager reload",
    "manager set debug",
    "manager show command ",
    "manager show event ", /* здесь простые команды заканчиваются, элемент массива № 3  */
    "manager show connected",
    "manager show eventq",
    "manager show events",
    "manager show settings",
    "manager show users",
    "manager show user ");
    
    for ($i=0;$i<count($ar);$i++) 
        {
        if (strpos($a,$ar[$i])!==false) 
            {
                $b = $i;
            } 
        }
    switch (true)
        {
        case $b==-1: /* если мы не нашли команду то мы уходим из функции выдавая пустой массив  */
            $result[0]["Name"] = "Error";
            $result[0]["Function"] = "cli";
            $result[0]["Number"] = "00001";
            $result[0]["Description"] = "Не найдена CLI команда";
            return $result;
            break;
        case $b<=3:
            return asterisk_cli_exec($a);
            break; 
        }
    }

var_dump(cli ('manager set debfsugfsdf')); /* заведомо ложная команда */
var_dump(cli ('manager set debug'));
var_dump(cli ('manager reload'));
?> 

Результат выполнения

array(1) {
  [0]=>
  array(4) {
    ["Name"]=>
    string(5) "Error"
    ["Function"]=>
    string(3) "cli"
    ["Number"]=>
    string(5) "00001"
    ["Description"]=>
    string(38) "Не найдена CLI команда"
  }
}
array(1) {
  [0]=>
  string(20) "manager debug is off"
}
array(0) {
}
 



Ну что же, продолжим дальше анализ данных поступающий от Астериска.

Беглый взгляд позволяет найти «похожие» ответы на команды.

Лично я составил несколько групп:

1 группа
manager show eventq
manager show settings
manager show user
(похожесть состоит в разделении ключей символом ":"-двоеточие)

2 группа
manager show commands
manager show events
(похожесть состоит в разделении ключей символом " "-пробелом(лами))

и без групп
manager show users
manager show connected

В связи с этим нам нужно реструктуризировать наш массив, где мы храним CLI команды, таким образом:

$ar = array ("manager reload",
    "manager set debug",
    "manager show command ",
    "manager show event ", /* здесь простые команды заканчиваются, элемент массива № 3  */
    "manager show eventq",  /* 1 группа */
    "manager show settings",/* 1 группа */ 
    "manager show user ",   /* 1 группа */
    "manager show commands",      /* 2 группа */
    "manager show events",        /* 2 группа */
    "manager show users",               /* Без группы */
    "manager show connected"            /* Без группы */
    );

Приступим к структуризации получаемых данных из 1 группы.

<?php

function asterisk_cli_exec ($a)
    {
        exec("asterisk -rx '$a'", $b);
        return $b;
    }
    
function asterisk_cli_passthru ($a)
    {
        passthru("asterisk -rx '$a'", $b);
        return $b;
    }
    
function cli ($a)
    {
    /*
    Матчасть:
    trim - удаляет пробелы в начале и конце статьи
    strtolower - преобразует строку в нижний регистр
    str_replace - удаляет двойные пробелы 
    */
    $a=str_replace("  ", " ",trim(strtolower($a)));
    /* переменую $b мы будем использовать в кострукции switch */
    $b=-1;
    /* Массив в котором мы будем хранить значения полученные из Астериска */
    $result = array ();
    /* массив где мы храним cli команды */
    $ar = array ("manager reload",
    "manager set debug",
    "manager show command ",
    "manager show event ", /* здесь простые команды заканчиваются, элемент массива № 3  */
    "manager show eventq",  /* 1 группа */
    "manager show settings",/* 1 группа */ 
    "manager show user ",   /* 1 группа */
    "manager show commands",      /* 2 группа */
    "manager show events",        /* 2 группа */
    "manager show users",               /* Без группы */
    "manager show connected"            /* Без группы */
    );

    for ($i=0;$i<count($ar);$i++) 
        {
        if (strpos($a,$ar[$i])!==false) 
            {
                $b = $i;
            } 
        }
    switch (true)
        {
        case $b==-1: /* если мы не нашли команду то мы уходим из функции выдавая пустой массив  */
            $result[0]["Name"] = "Error";
            $result[0]["Function"] = "cli";
            $result[0]["Number"] = "00001";
            $result[0]["Description"] = "Не найдена CLI команда";
            return $result;
            break;
        case $b<=3:
            return asterisk_cli_exec($a);
            break; 
        case $b<=6:
            /* Матчасть
            isset - проверяет существует ли переменная
            unset - разрушает переменную
            count - вычисляет длину массива
            explode - делит строку на подстроки используя разделитель, в итоге получается массив
            */
            $t = asterisk_cli_exec($a); /* Получаем результаты выполнения команды в временный массив $t */
            while (isset($t[0]))            
                {
                    if ($t[0]!="")      /* Проверяем не является ли текущая строка пустой*/
                        {
                            unset($m);    /* Уничтожаем переменную чтобы при повторном обращении не появились остаточные данные */
                            $m = explode(":", $t[0]); /* Делим строку на массив используя разделитель символ ":" */
                            if (isset($m[1])) /* Проверяем существует ли значение, если его нет то или это не наши данные а просто строка или это данные с пустым значением что нам также не нужно */
                                {
                                    $n = $m[1]; /* Присваиваем в переименую начало значения */
                                    for ($i=2;$i<count($m);$i++) 
                                        {
                                            $n.=":".$m[$i];  /* Если имеются еще элементы массива то мы восстанавливаем значение */
                                        } 
                                    $result[trim($m[0])]=trim($n); /* Присваиваем значение и ключ в наш массив */
                                }            
                        }
                    array_splice($t, 0, 1);
                }
            return $result;
            break; 
        }
    }

var_dump(cli ('manager show user admin'));
var_dump(cli ('manager show eventq'));
var_dump(cli ('manager show settings'));
?>


Результат выполнения

array(6) {
  ["username"]=>
  string(5) "admin"
  ["secret"]=>
  string(5) "<Set>"
  ["acl"]=>
  string(3) "yes"
  ["read perm"]=>
  string(91) "system,call,log,verbose,command,agent,user,config,dtmf,reporting,cdr,dialplan,originate,all"
  ["write perm"]=>
  string(91) "system,call,log,verbose,command,agent,user,config,dtmf,reporting,cdr,dialplan,originate,all"
  ["displayconnects"]=>
  string(3) "yes"
}
array(8) {
  ["Usecount"]=>
  string(1) "1"
  ["Category"]=>
  string(1) "1"
  ["Event"]=>
  string(8) "Registry"
  ["Privilege"]=>
  string(10) "system,all"
  ["Timestamp"]=>
  string(17) "1426383271.261620"
  ["ChannelType"]=>
  string(3) "SIP"
  ["Domain"]=>
  string(13) "217.15.180.50"
  ["Status"]=>
  string(10) "Registered"
}
array(16) {
  ["Global Settings"]=>
  string(0) ""
  ["Manager (AMI)"]=>
  string(3) "Yes"
  ["Web Manager (AMI/HTTP)"]=>
  string(2) "No"
  ["TCP Bindaddress"]=>
  string(12) "0.0.0.0:5038"
  ["HTTP Timeout (minutes)"]=>
  string(2) "60"
  ["TLS Enable"]=>
  string(2) "No"
  ["TLS Bindaddress"]=>
  string(8) "Disabled"
  ["TLS Certfile"]=>
  string(12) "asterisk.pem"
  ["TLS Privatekey"]=>
  string(0) ""
  ["TLS Cipher"]=>
  string(0) ""
  ["Allow multiple login"]=>
  string(3) "Yes"
  ["Display connects"]=>
  string(3) "Yes"
  ["Timestamp events"]=>
  string(3) "Yes"
  ["Channel vars"]=>
  string(0) ""
  ["Debug"]=>
  string(2) "No"
  ["Block sockets"]=>
  string(2) "No"
}


Алгоритм развивается дальше в том же направлении и при использовании данных команд. К сожалению, коддеры Астера очень часто меняют вывод результатов команд, иногда даже по нескольку раз в течении одной версии. Для неповторяющихся результатов команд ветка алгоритма пишется отдельно.

В данной статье не рассмотрены 2 группа и неповторяющиеся команды. Но основная идея с реализацией видна.

Резюме


Написать модуль для связи Астериска с веб-приложением можно и для этого не требуются глубокие познания в Звездочке или PHP. Надеюсь, данная статья поможет вам понять, в какую сторону идти и как связать серверную часть приложения с Астериском.

p.s. Конечно, легче всего все реализовать на сокетах и AMI, но с функционалом CLI на начальном уровне знакомства с Астериском пока ничего не может сравниться. Разве что прямое редактирование конфигурационных файлов или SQL.
Защитникам ARI и его производных скажу сразу свое мнение, которое не претендует на истину в последней инстанции, реализация приложений на них требует более глубокого понимания Звездочки.

Просьба не кидаться камнями, я просто поделился своим опытом. Кстати, начинающим кодерам и VOIP-програмерам — «код полностью рабочий», проверено на Астере 13 версии.
Tags:
Hubs:
+3
Comments 0
Comments Leave a comment

Articles