Time-Zone Processing with Asterisk, Part II

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

The register state handler is called whenever a SIP registration event is received from a new extension. Its main purpose is to get the data required for setting up the configuration telephone call when a new extension pops up. Whether a call is made depends on the state of the extension as far as time-zone processing, so this routine requests information to determine whether the extension is registered, its IP address and other components. To get the extension, we have to take the channel name, which is prefaced with the technology and a slash (for example SIP/) and strip the leading part away.

One wrinkle of the event handler is that POE handlers run to completion. There is no way to interrupt a handler when it is running. The sub-procedure getTZChannelVars will request information on the time-zone offset and IP address, but that information will not become available until the registration handler completes and the responses return via the manager. At the end of the procedure, the registration handler uses the delay_set POE method to queue up the call state for a delay in the future so that the requests will have returned their information by that point. The delay is set by a global variable in the program. I have found that one second is more than adequate for a single-user PBX with only one outstanding extension requiring setup, but the delay is set to three seconds for safety.

Communication between state handlers is a bit different from that in a procedure-driven program. POE state handlers pass references to the POE kernel, which is used in scheduling, as well as the POE heap, which is needed to issue commands to the Asterisk Manager. POE defines constants so the heap and kernel are easily accessible to event handlers as $_[HEAP] and $_[KERNEL]. Any other information available is located at $_[ARG0], which is a constant defined in such a way that it is the first argument.

Any lines in the event that defines the state will be passed as the hash $_[ARG0] and are accessible by asking for the hash key that appears on the left-hand side of the desired line. In the registration response, it is possible to get at the peer extension by referring to $_[ARG0]->{Peer}, which returns SIP/300:

Event: PeerStatus
PeerStatus: Registered
Peer: SIP/300

On SIP registration, the program needs to identify the extension, request information about it and then set up further processing of the extension data after a delay. When an event is called through the delay_set method, it is possible to pass an extension to the state handler, such as the extension number used here:

sub register_state_handler {
  my $kernel = $_[KERNEL];
  # Split peer extension off from technology
  my $peer = $_[ARG0]->{Peer};
  debug_print("\tExtension is $peer; ");
  my @exten_parts = split('/',$peer);
  my $ext = @exten_parts[1];
  debug_print("extension number is $ext\n");

  getTZChannelVars($_[HEAP], $ext);

  debug_print("Queuing call event for ");
  debug_print("$REG_CALL_DELAY seconds\n");
  $kernel->delay_set("call", $REG_CALL_DELAY, $ext);

} # register_state_handler

As part of the extension registration process, we collect variables about the state of the channel in the getTZChannelVars procedure. The POE heap, which is passed as the first argument, can be used to issue commands to the manager. For example, the put argument to the server can be used to issue commands. To get the SIP peer data, which includes the current IP address of the peer, the command looks like this:

$heap->{server}->put({'Action' => 'SIPShowPeer',
                      'Peer' => $ext });

To get a database variable, the action in the put command is a DBGet. The time-zone data is stored as keys in the tz family, so it is necessary to specify both the family and assemble the correct key name, which is of the form 300-TIMESKEW or similar:

$heap->{server}->put({'Action' => 'DBGet',
                      'Family' => 'tz',
                      'Key' => $ext . '-TIMESKEW'});

Four database requests and the SIP peer data are requested by getTZChannelVars. Because this function is called by an event handler, it also is not interruptible. Therefore, it sends four database query events to the manager, but it does not process responses directly. (Complete code for the five requests within the full procedure is available on the Linux Journal FTP site.)

Command and Database Responses

In the gap between issuing requests and the time the call state is scheduled, responses flow in from the SIP data request and the database queries. From the SIP data request, we need to pick out the peer IP address, which appears on a line in the manager response reading Address-IP: Conveniently, the POE module parses out the lines in the response, so all we need to do is look for the Address-IP line by getting the value of the Address-IP hash element in one of the arguments passed to the handler. The POE heap is accessible across events, so adding the value of the SIP peer IP address to the heap makes it accessible to other event handlers:

sub response_state_handler {
  my $peer_ip = $_[ARG0]->{'Address-IP'};
  if (defined($peer_ip)) {
    debug_print("SIP context found; Peer IP address"
    debug_print("is $peer_ip\n");
} # response_state_handler

After the SIP data response comes back, the four database queries should return responses. Responses to the queries look like this:

DBGetResponse: Success
Family: tz
Val: -8

The callback handler is triggered whenever there is a DBGetResponse: Success event from the manager, with an argument of a hash that has each of the lines in the packet. Our interest is in the key and value lines, which can be retrieved from the arguments passed to the state handler. As with the previous handler, responses are stored in the POE task heap to make it available to other handlers:

sub db_response_state_handler {
  my $family = $_[ARG0]->{'Family'};
  my $key = $_[ARG0]->{'Key'};
  my $value = $_[ARG0]->{'Val'};

  if (defined($family)) {
     debug_print("Key $key in DB family $family");
     debug_print("has value = $value\n");
     # Store in heap
     $_[HEAP]->{$key} = $value;
} # db_response_state_handler