В сети распространено заблуждение, что дружба PHP и Asterisk CLI — это костыль. Возможно, это и так, но иногда по требованию заказчика для интеграции, например, с CRM системой приходится связывать с VOIP, и чаще всего это Asterisk.
Как же быстро и просто подвязать несколько команд из CLI, да так, чтобы можно было получать уже готовые массивы данных для передачи в веб-приложение?
На самом деле все очень просто. Немного рутиной работы, немного логики и, вуаля.
Для получения данных мы будем использовать документированную возможность Asterisk обращение к CLI из-вне.
Читаем матчасть:
Для запуска команды мы будем использовать две функции PHP, exec() и passthru().
В итоге мы получили две функции, которые дают нам 2 различных варианта для выдачи информации:
exec() — в виде массива строк;
passthru() — прямой вывод результата.
Функция asterisk_cli_passthru() может пригодиться только для создания собственного CLI интерфейса из браузера. В основном прямой вывод не требуется. Для web приложений нужны массивы. На данном этапе мы сразу же забудем про эту «вкусняшку», так как она уже полностью реализована, и более для нее ничего не требуется.
Итак перейдем к самому интересному.
Следующим шагом мы разделим команды на «простые» и «сложные». Простые команды это те которые в своем выводе не выдают ассоциативный массив данных.
Теперь начнем разбор полетов.
Простые функции у нас не будут обрабатываться, отдадим их как есть нашему клиенту.
Начнем коддинг.
Ну что же, продолжим дальше анализ данных поступающий от Астериска.
Беглый взгляд позволяет найти «похожие» ответы на команды.
Лично я составил несколько групп:
1 группа
manager show eventq
manager show settings
manager show user
(похожесть состоит в разделении ключей символом ":"-двоеточие)
2 группа
manager show commands
manager show events
(похожесть состоит в разделении ключей символом " "-пробелом(лами))
и без групп
manager show users
manager show connected
В связи с этим нам нужно реструктуризировать наш массив, где мы храним CLI команды, таким образом:
Приступим к структуризации получаемых данных из 1 группы.
Алгоритм развивается дальше в том же направлении и при использовании данных команд. К сожалению, коддеры Астера очень часто меняют вывод результатов команд, иногда даже по нескольку раз в течении одной версии. Для неповторяющихся результатов команд ветка алгоритма пишется отдельно.
В данной статье не рассмотрены 2 группа и неповторяющиеся команды. Но основная идея с реализацией видна.
Написать модуль для связи Астериска с веб-приложением можно и для этого не требуются глубокие познания в Звездочке или PHP. Надеюсь, данная статья поможет вам понять, в какую сторону идти и как связать серверную часть приложения с Астериском.
p.s. Конечно, легче всего все реализовать на сокетах и AMI, но с функционалом CLI на начальном уровне знакомства с Астериском пока ничего не может сравниться. Разве что прямое редактирование конфигурационных файлов или SQL.
Защитникам ARI и его производных скажу сразу свое мнение, которое не претендует на истину в последней инстанции, реализация приложений на них требует более глубокого понимания Звездочки.
Просьба не кидаться камнями, я просто поделился своим опытом. Кстати, начинающим кодерам и VOIP-програмерам — «код полностью рабочий», проверено на Астере 13 версии.
Как же быстро и просто подвязать несколько команд из CLI, да так, чтобы можно было получать уже готовые массивы данных для передачи в веб-приложение?
На самом деле все очень просто. Немного рутиной работы, немного логики и, вуаля.
Для получения данных мы будем использовать документированную возможность Asterisk обращение к CLI из-вне.
Читаем матчасть:
asterisk -rx вызов команды CLI извне.
-r: Соединяемся с запущенным в фоне сервером Asterisk, и получаем доступ к CLI консоли
-x: В комбинации с параметром -r, выполняет команду CLI Asterisk
Пример: asterisk -rx «sip reload» — произведет рестарт sip.
-x: В комбинации с параметром -r, выполняет команду CLI Asterisk
Пример: asterisk -rx «sip reload» — произведет рестарт sip.
Запрет цветного вывода в консоли.
У звездочки есть еще один полезный ключ:
-n: Запретить поддержку цветного вывода ANSI
Но как я не пытался отключить цветной вывод, это у меня не получилось, видать руки кривоватые.
-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»
}
[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
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»
}
[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 версии.