For the complete documentation index, see llms.txt. This page is also available as Markdown.

Interact with AMI

Listen to live Asterisk events and send AMI commands from a module worker.

The Asterisk Manager Interface (AMI) is a TCP protocol exposed by Asterisk on 127.0.0.1:5038. It lets you do two things from a module:

  • Listen to a live stream of events (Newchannel, DialBegin, Hangup, UserEvent, peer/extension status, …).

  • Send actions (Originate, Hangup, SetVar, Command, …) and read the response.

MikoPBX never makes you open a raw socket. The Core gives you a fully wired MikoPBX\Core\Asterisk\AsteriskManager instance through MikoPBX\Core\System\Util::getAstManager(), already connected and logged in with the system AMI credentials.

This recipe threads the running example module ModuleBlackList (config class BlackListConf, main class BlackListMain, model BlackListNumbers) and anchors every pattern on two real, working modules:

  • Extensions/EXAMPLES/AMI/ModuleExampleAmi/ — the canonical AMI example.

  • Extensions/ModuleCallTracking/ — a production module that listens for a single UserEvent and forwards it over HTTP.

A long-lived AMI listener belongs in a background worker, not in a controller or a REST callback. See Background workers for the worker lifecycle, and AMI / AJAM for the protocol reference.

Two connection modes: listener vs commander

Util::getAstManager() resolves one of two shared DI services depending on the $events argument:

Core/src/Core/System/Util.php
public static function getAstManager(string $events = 'on'): AsteriskManager
{
    if ($events === 'on') {
        $nameService = AmiConnectionListener::SERVICE_NAME; // 'amiListener'
    } else {
        $nameService = AmiConnectionCommand::SERVICE_NAME;  // 'amiCommander'
    }
    // returns the shared, already-connected manager from the DI container
}
Call
DI service
Use it for

Util::getAstManager() / Util::getAstManager('on')

amiListener

A worker that subscribes to the event stream.

Util::getAstManager('off')

amiCommander

One-shot actions (Originate, Hangup, Command) where you do not want the event firehose.

The amiCommander provider connects to 127.0.0.1:<AMI port> (the port comes from PbxSettings::AMI_PORT, default 5038) with events turned off:

Both services are registered as shared in the DI container, so repeated getAstManager() calls hand back the same socket. You do not create or close the connection yourself.

Step 1 — Write the AMI listener worker

An AMI listener is a WorkerBase subclass whose start() method:

  1. obtains the listener connection via Util::getAstManager();

  2. installs event filters (setFilter()) — without them AMI sends almost nothing;

  3. registers a callback with addEventHandler();

  4. blocks in a while (true) loop on waitUserEvent(true), reconnecting when the loop returns an empty array.

Here is the ModuleBlackList worker, modelled directly on the real example.

See the working originals: Extensions/EXAMPLES/AMI/ModuleExampleAmi/Lib/WorkerExampleAmiAMI.php (broadcasts events to the web UI over the EventBus) and Extensions/ModuleCallTracking/Lib/WorkerCallTrackingAMI.php (forwards a single UserEvent over HTTP).

Why each piece is there

  • require_once 'Globals.php'; — bootstraps the module autoloader/DI so the worker can run as a standalone CLI process.

  • setFilter() is not optional. A freshly logged-in AMI connection with an event filter installed receives only what you explicitly add. The Core's own example puts it bluntly: "Without explicit filters, AMI only sends login/ping responses." Add UserEvent: <name> filters for user events and Event: <Name> filters for native Asterisk events.

  • makePingTubeName() + replyOnPingRequest() come from WorkerBase. The monitor (WorkerSafeScriptsCore) pings the worker by sending a UserEvent whose name equals makePingTubeName(static::class); replyOnPingRequest() detects it and answers with a …Pong UserEvent. If you skip the ping filter, the monitor concludes the worker is dead and restarts it in a loop.

  • waitUserEvent(true) blocks reading the socket, runs each event through the registered handler, and returns [] on timeout — your cue to reconnect. It loops internally (do { … } while (!$timeout)) and returns only when a ping fails, so it does not hand control back once per event. The body of your while (true) loop therefore executes only on disconnect. Put per-event work in the handler and periodic work in setOnIdleCallback(); anything you place in the outer loop expecting it to run regularly will never run at all on a healthy PBX — and will look fine in testing for exactly that reason.

Subscribing to native Asterisk events

To watch call progress instead of (or in addition to) custom user events, add Event: filters and handle '*':

Extensions/EXAMPLES/AMI/ModuleExampleAmi/Lib/WorkerExampleAmiAMI.php filters exactly this set (Newchannel, Newstate, DialBegin, DialEnd, Hangup, PeerStatus, ExtensionStatus, Hold, Unhold, Bridge) and registers its handler against '*' so every one of them reaches the callback.

Step 2 — Register the worker for monitoring

Workers do not start themselves. Declare them from your config class via getModuleWorkers() so the Core supervisor (WorkerSafeScriptsCore) launches and watches them. For an AMI listener use the CHECK_BY_AMI strategy — that is the ping mechanism the worker already answers in callback().

WorkerSafeScriptsCore::CHECK_BY_AMI is the literal string 'checkWorkerAMI' (defined in Core/src/Core/Workers/Cron/WorkerSafeScriptsCore.php). The other strategy, CHECK_BY_BEANSTALK ('checkWorkerBeanstalk'), is for queue workers, not AMI listeners.

Confirm against Extensions/EXAMPLES/AMI/ModuleExampleAmi/Lib/ExampleAmiConf.php, which registers WorkerExampleAmiAMI with exactly this shape.

CHECK_BY_AMI is what wires the liveness ping to the makePingTubeName() / replyOnPingRequest() pair in your worker. The two go together: pick CHECK_BY_AMI, add the ping filter, answer the ping.

Step 3 — Send AMI commands

To act on the PBX, grab a manager connection and call the typed methods on AsteriskManager. All of these are real methods on Core/src/Core/Asterisk/AsteriskManager.php.

Originate a call

Originate() accepts named parameters ($channel, $exten, $context, $priority, $application, $data, $timeout, $callerid, $variable, $account, $async, $actionid). Empty parameters are stripped before the request is sent, and Async is normalized to 'true'/'false' for you.

Hang up a channel

Run a CLI command or a raw action

AsteriskManager also exposes Command() (wraps the AMI Command action) and sendRequestTimeout($action, $params) for any action that has no typed wrapper:

A complete request router — accepting either a CLI string or a multi-line Action: … block over REST — lives in Extensions/EXAMPLES/AMI/ModuleExampleAmi/Lib/ExampleAmiConf.php (sendAmiCommandAction(), sendNativeAmiAction()).

Step 4 — Provision a dedicated AMI user (optional)

The shared Util::getAstManager() connection uses the system AMI account, which is enough for most modules. If you need your own AMI user (for example, a third-party app that connects directly), override generateManagerConf() on your config class. The Core appends its return value to manager.conf.

generateManagerConf() is a real hook declared in Core/src/Core/Asterisk/Configs/AsteriskConfigInterface.php and overridden here. ManagerConf::reload() (Core/src/Core/Asterisk/Configs/ManagerConf.php:258-270) regenerates manager.conf — which is what invokes your generateManagerConf() hook again — plus http.conf, then issues module reload manager and module reload http. ExampleAmiConf::generateManagerConf() (Extensions/EXAMPLES/AMI/ModuleExampleAmi/Lib/ExampleAmiConf.php:269) is the working version of the hook itself.

Reference: methods used in this recipe

All confirmed against the pinned Core sources.

Symbol
Where
Purpose

Util::getAstManager(string $events = 'on')

Core/src/Core/System/Util.php

Get the shared listener ('on') or commander ('off') connection.

AsteriskManager::sendRequestTimeout(string $action, array $params = [])

AsteriskManager.php

Send any AMI action and read the response array.

AsteriskManager::waitUserEvent(bool $allow_timeout = false)

AsteriskManager.php

Block on the event stream; returns [] on timeout.

AsteriskManager::addEventHandler(string $event, array|string $callback)

AsteriskManager.php

Register a per-event handler (once per name).

AsteriskManager::Originate(...)

AsteriskManager.php

Originate a call (named args).

AsteriskManager::Hangup(string $channel)

AsteriskManager.php

Hang up a channel.

AsteriskManager::SetVar(string $channel, string $variable, string $value)

AsteriskManager.php

Set a channel variable.

AsteriskManager::Command(string $command, ?string $actionid = null)

AsteriskManager.php

Run a CLI command via the Command action.

WorkerBase::makePingTubeName(string $class)

Core/src/Core/Workers/WorkerBase.php

Compute this worker's liveness ping name.

WorkerBase::replyOnPingRequest(array $parameters)

WorkerBase.php

Answer a monitor ping; returns true if handled.

WorkerSafeScriptsCore::CHECK_BY_AMI

WorkerSafeScriptsCore.php

Monitoring strategy string 'checkWorkerAMI'.

ManagerConf::reload()

Core/src/Core/Asterisk/Configs/ManagerConf.php:258

Regenerate manager.conf + http.conf and reload both Asterisk modules.

PbxSettings::AMI_PORT

Core/src/Common/Models/PbxSettings.php

AMI TCP port setting (default 5038).

See also

Last updated

Was this helpful?