AMI Output format

Hello,

I am using php AMI to get outcome of commands from asterisk , and I want to the outcome in json format , just wanted to know in which format does asterisk returns the ami command .

Thanks in advance.

Regards
Chetan

http://asteriskdocs.org/en/3rd_Edition/asterisk-book-html-chunk/AMI_id248378.html#AMI_id248511

I’ve ignored AMI over HTTP because so few people use it that it is essentially unmaintained and you will have difficulty finding support.

I find the HTTP AMI interface to be very handy. I don’t have to write or maintain any raw sockets code, nor deal with request/response parsing (which historically could be awful, but has gotten better over the years). Any one who tried to use the AMI “Command” action in the old days knows how quickly you could get yourself into trouble.

It’s nice to be able to call HTTP endpoints to get things done. I would even say it makes a nice companion to “asterisk -rx” .

Finally, it allows for direct browser-based apps to be written, which is really cool (browsers can’t do raw sockets).

The provided ajamdemo.html and astman.js were all the support I needed :wink:

  • Darrin

  • Darrin

Recently jcolp, who is the main Sangoma person responding here, had actually forgotten that the HTTP option existed.

Indeed. Seldom used, rarely seen, often forgotten.

Parsing AMI responses is easy, which is why so many people have done it. Exposing Asterisk HTTP directly to a client browser doesn’t seem like a good idea, for obvious reasons.

Or you could use a prebuilt wrapper that handles it all for you, and provides the option for SSL/TLS connections and asynchronous calls as well.

but how do i parse it and output the same in json format

Encoding JSON is quite easy.

I am using php ami and when i get the outcome i tried to do it using json_encode($output);

it does not returns output in json format, am I doing anything incorrect?

Regards
Chetan

Have you checked the docs?

Yes tried but still unable to get in json format, probably i am doing something wrong, below is my code snippet

<?php function get_channel_stats($username, $password) { $errno = NULL; $errstr = NULL; $socket = fsockopen("localhost","5038", $errno, $errstr); fputs($socket, "Action: Login\r\n"); fputs($socket, "UserName: $username\r\n"); fputs($socket, "Secret: $password\r\n\r\n"); fputs($socket, "Action: Command\r\n"); fputs($socket, "Command: core show channels\r\n\r\n"); fputs($socket, "Action: Logoff\r\n\r\n"); $i=0; while (!feof($socket) && $i<300) { $wrets = fgets($socket, 8192); $i++; echo json_encode($wrets); } fclose($socket); //usleep(10); } get_channelstats('cxadmin', 'P@ssw0rd@cx');

Marked up properly:

<?php

function get_channel_stats($username, $password)
{
   $errno = NULL;
    $errstr = NULL;
    $socket = fsockopen("localhost","5038", $errno, $errstr);
    fputs($socket, "Action: Login\r\n");
    fputs($socket, "UserName: $username\r\n");
    fputs($socket, "Secret: $password\r\n\r\n");
    fputs($socket, "Action: Command\r\n");
    fputs($socket, "Command: core show channels\r\n\r\n");
    fputs($socket, "Action: Logoff\r\n\r\n");
    $i=0;
    while (!feof($socket) && $i<300)
      {
         $wrets = fgets($socket, 8192);

         $i++;
         echo json_encode($wrets);
      }
      fclose($socket);

      //usleep(10);
   
  
}

get_channelstats('cxadmin', 'P@ssw0rd@cx');

You are not making any attempt to parse the incoming responses. firstly into responses, and secondly into the individual fields within a response.

You are generating a sequence of values with no punctuation (no start and end array syntax, and no value delimited syntax).

You are pipelining commands in a single threaded process. You will almost certainly get away with this as the buffers probably won’t saturate. but, theoretically, it could deadlock.

channelstats is a confusing name, as there are channelstats functions that read lower level information about SIP channels.

(I thought I came across an example of the HTTP interface to AMI returning JSON, but I can no longer find it, so may be mistaken. In any case, I would not expect it to return compound values as other than a single string literal.))

(What your code appears to do is to take each line returned, including blank lines, and wrap them each up as a separate JSON value, which will be as string, so I’d expect it to add quotes, and insert escape codes for problem characters, including the newline at the end of each string.

What I guess you wanted, was for it return an array of responses, removing any unsolicited events, and for each response, to return a structure, with the field names being the tag codes, and the values being appropriate data types (possibly inlcuding structures or arrays, themselves). For that, you will have to parse the response to a similar level of detail, before attempting to encode it.)

Actually, I’ve just realised that you are using AMI to run a CLI command, which means you would have to parse each line of the response into fields. and also that this question is more about CLI output formats, than AMI ones. Even if you use the AMI native CoreShowChannels, you will get one response for each channel, so you would need to add an array, or structure level, into the JSON, for those, and discard the trailer record.

I assume that the responses will be synchronous, so it won’t be necessary to use an action ID, although, to be on the safe side, you would probably have to.

With what you are actually doing you will only get one response, bu it will have lots of repeated “Output:…” lines, which will make even less sense to simply code as a JSON string.

I’d also note that you are not passing any options to “core show channels”. Whilst proper AMI actions produce output intended for machine use, the CLI normally provides output intended only for direct human use, and the AMI “Command” will simply pass that through.

As well as not being machine friendly, the default output uses fixed length fields, that can result in information being truncated.

However, there is a concise option, on core show channels, that produces more machine friendly output, although possibly not with the fields you want.

If you are only using AMI for core show channels, you should consider whether it might be easier to do a popen to “asterisk -x” instead. Arguably the AMI route avoids launching a new Asterisk instance in console mode, so may use less system resources, but it definitely complicates the programming.

Using CoreShowChannels, rather than Command core show channels, in AMI, is probably better, though.

Parsing output from a Command action isn’t that hard. Here’s an excerpt from my Python wrapper (slightly simplified) to show how it’s done:

async def do_command(self, command) :
    "does a Command request and returns the response text."
    await self.send_request("Command", {"Command" : command})
    response = ""
    first_response = True
    status = None
    while True :
        while True :
            if self.buff.find(self.NL) >= 0 or self.EOF :
                break
            more = await self.sock.recv(IOBUFSIZE, self.timeout)
            if len(more) == 0 :
                self.EOF = True
                break
            #end if
            self.buff += more.decode()
        #end while
        if self.buff.find(self.NL) < 0 :
            break
        line, self.buff = self.buff.split(self.NL, 1)
        if len(line) == 0 :
            break
        items = line.split(": ", 1)
        if len(items) == 2 :
            if items[0] == "Response" :
                assert first_response
                status = items[1]
                if status not in ("Follows", "Success") :
                    raise RuntimeError \
                      (
                        "Command failed -- %s" % (status,)
                      )
                #end if
                first_response = False
            elif items[0] == "Output" :
                assert not first_response
                response += items[1] + "\n"
            #end if
        #end if
    #end while
    return response
#end do_command

I remember this being more complicated in earlier versions of Asterisk, but the output format seems a bit cleaner these days.

My wrapper also has the option to communicate directly over an Asterisk console connection, if you want.

I think the OP will want this as an array of structures, not as a single string. He’ll also want it JSON encoded, although, once you have an array of structures, that is probably easy. He’ll also want the header line stripped.

Also remember that they pipelined the requests, so the first empty line will be that after the login response. Even if they didn’t pipeline, unless they turned off all event permissions, there could be events received between when the login completed and Asterisk received the Command request.

One point I failed to mention is that only the concise format has any guarantee of stability. The other formats may change between versions.

In terms of event confusion, this is why, as a general rule, you really don’t want to use the same AMI connection for transacting requests and for listening for events.

I think the main reason we ran two connections was more that asynchronous events get held back whilst an action is being processed.

However, I suspect most people asking about AMI here probably haven’t realised that “read” really refers to asynchronous events, not to reading, so are likely to have events mixed in.

Thanks got it working with the below code

@methods.add
def ShowQueuesSummary():
def event_listener(event,**kwargs):
global EventCollection,client,adapter
EventCollection[event.keys[‘ActionID’]].append(event)
myactionid = str(uuid.uuid4())
EventCollection[myactionid] =
mylistener1 = client.add_event_listener(event_listener,white_list=[‘QueueSummary’,‘QueueSummaryComplete’])
def cancel_wait(signum,frame):
raise Exception(“QueueSummary Failed”)

signal.signal(signal.SIGALRM, cancel_wait) 
signal.alarm(2)  
try: 
    resp = adapter.QueueSummary(ActionID=myactionid)
    if resp.response is None: 
        client.remove_event_listener(mylistener1)
        EventCollection.pop(myactionid, None)
        return []
    while 'QueueSummary' not in [keys.name for keys in EventCollection[myactionid] ]: pass
    signal.alarm(0)
    channels = []
    for event in EventCollection[myactionid]:
        if event.name == "QueueSummary":
            channels.append(event.keys)
    client.remove_event_listener(mylistener1)
    EventCollection.pop(myactionid, None)
    return channels
except Exception as e:
    print('QueueSummary failed: ',e)
    client.remove_event_listener(mylistener1)
    EventCollection.pop(myactionid, None)
    return []

There seems to be this fondness for constructing elaborate UUIDs to uniquely identify requests on an AMI connection. Me, I just use a simple incrementing counter. :wink: