Time-Zone Processing with Asterisk, Part II

Part II of our series on time-zone processing with Asterisk.

Last month, I wrote about a system for handling telephone calls with Asterisk that automatically handled the call depending on the time of day at a remote location. Use of the system, however, depended on the user performing the critical task of setting up the remote location's time with a telephone call. Rather than rely on the user to initiate the telephone call manually, it would be easier if the call occurred automatically.

If the setup call occurs automatically, the user won't forget to do it. The initial SIP registration may occur at a very strange hour in the home location, but the SIP registration occurs because a user has plugged something in. Therefore, we know the user is awake and can take a short call. Asterisk provides a management interface that reports when SIP registrations occur and can be used to take action based on it. With a bit of additional processing, a script talking to the manager can initiate calls only when the SIP registration is “new”.

Introduction to the Asterisk Manager

The Asterisk Manager reports events processed by Asterisk and accepts commands over the interface. The form of the interface is a text-based protocol that separates event reports and commands into clusters of lines with a key: value format. For example, the registration of extension 300 using the SIP protocol looks like this:

Event: PeerStatus
PeerStatus: Registered
Peer: SIP/300

Before gaining access to the Asterisk manager, clients must authenticate against the list of administrative users stored in /etc/asterisk/manager.conf. Once logged in, a client can issue commands or query the value of variables with a set of lines that starts with a first line of Action:, followed by a command. Responses to commands typically start with Response: Success.

Because the protocol is text-based, it can be scripted in a language like Expect. A component is also available for the Perl Object Environment (POE), a framework that builds event-driven programs in Perl. The freely available component provides the base-level response parsing that would need to be written in Expect, so it is a much more extensible foundation for programs controlling the Asterisk manager.

The Program Core and Inline States

The main code for the program is simple. POE sets up a system where state handlers are called in response to program states. A state can be defined by the programmer or by an external event. The typical flow through the program is to notice a SIP registration, check to see whether it has an active time-zone registration and if not, to initiate a configuration call.

To execute code in response to an event, the POE framework uses a hash called CallBacks. Every entry in CallBacks defines a state based on the event received from the manager. When an event matches a callback, the handler defined for the state is triggered. To set up a trigger with the CallBacks clause, identify every line in the event and set up a hash so that the left-hand side of each line of the event is the key value for a line of the hash. As an example, consider the callback definition for the SIP registration event earlier:

Event: PeerStatus      register => { 'Event' => 'PeerStatus',
PeerStatus: Registered               'PeerStatus' => 'Registered', }
Peer: SIP/300

To link the callback to a handler, the inline_states hash has a list of states and references to the corresponding code to call. Although it is possible to inline event-handler code, for readability I have separated the code out into external procedures. Code called in response to a CallBack cannot be passed arguments:

inline_states => {
     register => \&register_state_handler,

Based on the flow of the program, three events are of interest. First, SIP registration events are used to start the entire process. SIP registrations typically occur hourly, so it is important to initiate the call only when a registration is the “first” registration. To prevent duplicate telephone calls from being initiated, the program will request data from both the Asterisk internal database AstDB as well as the SIP peer information. The second and third events will handle responses to commands and database queries, respectively. A fourth event will handle initiating the telephone call after receiving data back from the queries. The code I am currently using also has a state defined for unregister events, though it is a stub for an event that I am not currently using.

The core of the program is only 35 lines, most of which defines the program event states and shows what code will be used in response to those states. Note that the state of call is defined by the program and not by a callback, so the call state can be entered only by the program itself and not in response to an event from the manager. (A full listing of the program is available on the Linux Journal FTP site; see Resources.)

   Alias           => 'monitor',
   RemoteHost      => 'localhost',
   RemotePort      => 5038,
   Username        => 'autotzcaller',
   Password        => 'secretpassword',
   CallBacks  => {
        input    => ':all',
        response => {
                'Response' => 'Success',
        dbresponse => {
                'Event' => 'DBGetResponse',
        register => {
                'Event' => 'PeerStatus',
                'PeerStatus' => 'Registered',
        unregister => {
                'Event' => 'PeerStatus',
                'PeerStatus' => 'Unregistered',
   inline_states => {
        input      => \&input_state_handler,
        response   => \&response_state_handler,
        dbresponse => \&db_response_state_handler,
        register   => \&register_state_handler,
        call       => \&call_state_handler,
        unregister => \&unregister_state_handler,


Two of the state handlers are only stubs. The input state handler prints out whatever it gets if a debug flag is set, and it is there for development purposes. It catches any unrecognized events that come from the manager, and it can be useful when testing that callbacks are catching the important events. The unregister state handler currently doesn't do anything, but it is there as a hook to expand if I choose in the future to take any action based on that.

With the core of the program in place, let's look at each of the states in the order they will be called through a typical program execution flow.