Provisioning a SPA8000 ATA Using FreePBX OSS Endpoint Manager
The Linksys/Cisco (and Sipura?) SPA8000 is an eight-port VoIP ATA: it allows you to register up to eight analog telephones to a VoIP provider. They are the cheapest ATA I know of that provides eight FXS ports, and we use them extensively to provide VoIP "lines" to our Norstar system.
Recently, we set up a SPA8000 to provide regular analog telephones using Asterisk with FreePBX. Provisioning this box automatically was a trial that required diving into the FreePBX source code, so I am documenting what I learned in public.
The SPA8000 is weird because it is essentially four two-port SPA2102 ATAs hooked together. Lines one and two come from the first ATA. The other six lines come from three ATAs networked "behind" the first one. They use NAT to traverse the first ATA and connect to the VoIP provider.
Most VoIP providers provide SIP traffic via port 5060. This works for Line 1 (and maybe Line 2?) but the other lines cannot use this port. Instead, by default the ATA gives them other port numbers: 5060 for line 1, 5061 for line 2, 5160 for line 3, 5161 for line 4, 5260 for line 5 and so on.
The FreePBX web interface comes with a largely-unsupported provisioning system called the "OSS Endpoint Manager". (Sangoma, the company which produces FreePBX, has its own commercial provisioning system, which might explain why they are not that enthusiastic about supporting OSS Endpoint Manager.) The job of the Endpoint Manager is to "provision" phones by taking extension and registration information already stored in Asterisk/FreePBX and make it available to VoIP handsets and ATAs. When these handsets and ATAs boot, they pick up the generated configuration files and use them to configure themselves appropriately.
To do this, the configuration manager takes template files and populates them with relevant registration information. It then drops these template files into a TFTP folder for the handsets/ATAs to pick up.
Supposedly, the SPA8000 is supported by OSS Endpoint manager. Unfortunately, it does not work right, because the existing template sets the server port for every line to the same value: 5060. This might work for lines 1 and 2, but lines 3-8 won't be able to register with this template. My job was to fix the template so that all the lines would register.
(My other option was to forget about the OSS Endpoint Manager entirely and manually enter registration information into the SPA8000. This would have worked, but it makes working with the FreePBX interface messy in other ways, because FreePBX won't know that the extensions associated with the SPA8000 are already registered to a device.)
As of this writing, the version of FreePBX is 12.0.76.2 . The version of Endpoint Manager is 2.11.13 . The version of Asterisk is 11.13.1 . Who knows what the future will bring?
Here is what little I learned.
Templates and Variables
The primary template for the SPA8000 (and a bunch of other Linksys devices)
are in two files $model.cfg
and spa$mac.xml
. On my installation,
I found these templates in the following folder:
/var/www/html/_ep_phone_modules/endpoint/cisco/linksysata/
.
Supposedly the $model.cfg
is only used for initial configuration,
and so is not that helpful to us. spa$mac.xml
is the important file.
In spa$mac.xml
you will find some stanzas with line settings. Here
is the loop indicator and the first stanza:
{line_loop}
<!-- Line Registration Information -->
<Display_Name_{$line}_ ua="na">{$displayname}</Display_Name_{$line}_>
<User_ID_{$line}_ ua="na">{$username}</User_ID_{$line}_>
<Password_{$line}_ ua="na">{$secret}</Password_{$line}_>
<Dial_Plan_{$line}_ ua="na">{$dial_plan}</Dial_Plan_{$line}_>
<SIP_Port_{$line}_ group="Ext_{$line}/SIP_Settings">{$server_port}</SIP_Port_{$line}_>
<Register_Expires_{$line}_ group="Ext_{$line}/Proxy_and_Registration">{$server_expires|3600}</Register_Expires_{$line}_>
<Ans_Call_Without_Reg_{$line}_ ua="na">{$answer_call_without_reg|No}</Ans_Call_Without_Reg_{$line}_>
There are clearly variables within curly braces, and the one we are
interested in looks like {$server_port}
. If we can change this per
line then we are golden.
Not so fast, cowboy. While the endpoint manager does provide
mechanisms for setting variables, in this case they are irrelevant. In
/var/www/html/endpointman/includes/functions.inc
there is the
following code snippet:
//Set Variables according to the template_data files included. We can include
// different template.xml files within family_data.xml
// also one can create template_data_custom.xml which will get included or
// template_data_<model_name>_custom.xml which will also get included
//line 'global' will set variables that aren't line dependant
$provisioner_lib->settings = $new_template_data;
//Loop through Lines!
$li = 0;
foreach ($phone_info['line'] as $line) {
$line_options = is_array($line_ops[$line['line']]) ? $line_ops[$line['line']] : array();
$line_statics = array('line' => $line['line'], 'username' => $line['ext'],
'authname' => $line['ext'], 'secret' => $line['secret'], 'displayname' => $line['description'],
'server_host' => $this->global_cfg['srvip'], 'server_port' => '5060',
'user_extension' => $line['user_extension']);
$provisioner_lib->settings['line'][$li] = array_merge($line_options, $line_statics);
$li++;
}
Most of the variables (eg username
, secret
, displayname
) are
specified in some kind of $line
array. Meanwhile, server_port
is
hardcoded to be 5060. I spent a bunch of time trying to work around
this by specifying $server_port
in a file, with no success.
To make a long story short, I decided to hack around the problem by
coming up with a new variable, $line_sip_port
. This variable should
have a different value for each line. The comments suggest that there
are line-dependent variables, and the array_merge
call suggests that
we can (somehow) merge new variables into the configuration if we can
get them into $line_options
.
To use this variable, I changed the server_port
line in
spa$mac.xml
from:
<SIP_Port_{$line}_ group="Ext_{$line}/SIP_Settings">{$server_port}</SIP_Port_{$line}_>
to
<SIP_Port_{$line}_ group="Ext_{$line}/SIP_Settings">{$line_sip_port|5060}</SIP_Port_{$line}_>
The |5060
indicates that if $line_sip_port
does not exist then the
default value "5060" should be used for the port.
Specifying Variables
How do you create new variables? How do you specify them so they are different for each line? This is where things get really messy, and although I got something working I do not understand the mechanisms very well.
First, variables. In the file
/var/www/html/_ep_phone_modules/endpoint/cisco/linksysata/family_data.json
there is a file which specifies options for each phone model in the
family. The entry for the SPA8000 appears to be:
{
"model": "SPA8000",
"lines": "8",
"id": "7",
"template_data": [
"template_data.json",
"keys.json"
]
},
If we look at template_data.json
we see a complicated JSON file that seems to specify variables.
These variables apply globally. For example, here is $enable_webserver
:
{
"variable":"$enable_webserver",
"default_value":"Yes",
"description":"Enable Webserver",
"type":"radio",
"data":[
{
"text":"Yes",
"value":"Yes"
},
{
"text":"No",
"value":"No"
}
]
},
There is a different JSON file several levels up, in
/var/www/html/_ep_phone_modules/endpoint/global_template_data.json
.
It contains intriguing stanzas with per-line options. Here is an
excerpt:
{
"description": "Line Options",
"type": "loop_line_options",
"data": {
"item": [
{
"variable": "$line_enabled",
"default_value": false,
"description": "Enable Line {$count}",
"type": "checkbox"
},
{
"variable": "$displayname",
"default_value": "",
"description": "Display Name",
"type": "input"
},
{
"variable": "$username",
"default_value": "",
"description": "Username Name",
"type": "input"
},
}
}
$displayname
and $username
are defined per line, so this is
promising. The magic seems to be the line
"type": "loop_line_options",
and the variable {$count}
.
Thus, to add new variables, I made a new JSON file using the
"loop_line_options" to populate the server ports. I called this file
spa8000_lines.json
and modified family_data.json
to include it:
{
"model": "SPA8000",
"lines": "8",
"id": "7",
"template_data": [
"template_data.json",
"spa8000_lines.json",
"keys.json"
]
},
That was the easy part. The hard part was actually crafting the JSON file. I have no idea how the provided files are crafted (I expect using some automated process), but I ended up hacking together a hand-coded file. One of the hardest parts was figuring the proper nesting levels. Here is the file I came up with:
{
"template_data":{
"category":[ {
"name":"lines-level-0",
"subcategory":[ {
"name":"Line Settings Level 1",
"item":[ {
"type":"loop_line_options",
"description":"Line Ports Level 4",
"data":{
"item":[ {
"variable":"$line_sip_port",
"default_value":"506{$count}",
"description":"Line {$count} SIP Port",
"type":"input"
}]
}
}]
} ]
} ]
}
}
The default_value
entry is a terrible hack. It appends the line
number {$count}
to the string 506
to produce the ports 5061 to
5068 for the eight lines. This will break dramatically for ATAs with
greater than nine lines, but I could not figure out a good way of
doing the math properly.
It is not strictly necessarily to prepopulate the default_value
with
different values for each line. This JSON snippet makes a new screen
in the endpoint manager,
and it is possible to set these values manually for each line. That is
error-prone, however, so I prefer my terrible hack.
Line Loop Options and Lines
In the process of figuring out the JSON files, I learned another tidbit of documentation which is worth documenting. The magic for the SPA8000 ports was the following line:
"type": "line_loop_options"
- There is another type available: "loop". Here is an example, taken
- from
/var/www/html/_ep_phone_modules/endpoint/polycom/spom/linekeys.json
{ "description":"Line Keys", "type":"loop", "loop_start":"1", "loop_end":"12", "data":{ "item":[ { "variable":"$lineops_linekeys", "default_value":"1", "description":"LineKeys For Line {$count}", "type":"input" }, { "type":"break" } ] } }
This type requires you to specify "loop_start" and "loop_end" elements
as well. At first I thought this was the secret to setting server
ports, but it did not work, because the loop variable {$count}
was
not specific to a line.
I do not think that there are other secret types, based on
functions.inc
. There are case statements to parse loop
and
line_loop_options
(and lineloop
, which I think is the same thing?)
in that file, but there is nothing else.