ГЕОРГИЙ ТОЛОКОННИКОВ
Биллинг для АТС на базе PostgreSQL
В статье рассматривается содержащая около 90 строк кода биллинговая система для небольших АТС, которая легко может быть расширена на АТС большой номерной емкости. В качестве примера рассматривается NEAX2000 IPS.
Стандартный выход АТС соединяется с портом компьютера, приходящая от АТС информация захватывается скриптом биллинговой системы, обрабатывается, и направляется в базу данных телефонных переговоров, поддерживаемую СУБД PostgreSQL. На основе базы данных ведется подсчет трафика, расчет оплаты и выполняются практически любые запросы, интересующие пользователей.
Обычный веб-интерфейс позволяет выводить на экраны (удаленных) компьютеров результаты запросов в режиме реального времени.
Введение
Биллинговая система – это комплекс программ, работающий на компьютере, сопряженном с АТС и позволяющий тарифицировать телефонный трафик, выставлять счета абонентам, получать практически любую информацию по звонкам.
Такие системы обычно дороги. Многие пользуются Win Tarif (как правило, взломанной версией…). Можно также посмотреть сайт www.billonline.ru со всеобъемлющим сертифицированным Министерством связи решением проблем (за деньги) учета телефонных переговоров.
Наш подход, однако, – демонстрация философии UNIX в действии – позволяет обеспечить простое, бесплатное, открытое решение. Состоит оно из нескольких десятков строк кода и опирается на типовые утилиты UNIX. Сложность кода минимальная, достаточно, например, владения азами SQL и Perl. Обычно оператор связи, предоставивший телефоны, выставляет счет раз в месяц, а тарификацию по внутренним номерам офисной АТС либо вообще отказывается делать, либо требует дополнительной оплаты. Платное программное обеспечение имеет закрытый код и не ясно, что и как оно считает, заменить в нем что-нибудь на более подходящий вариант невозможно. Таким образом, наличие подобного предлагаемого в статье простого решения полезно для работы организации.
В настоящее время в небольших фирмах – операторах связи или в офисах крупных (и не очень) компаний наряду в ЛВС имеется АТС, часто попадающая на обслуживание системному администратору. Так что затронутые в статье вопросы многим окажутся интересны.
План действий по автоматизации тарификации АТС следующий:
- смотрим, что и в каком формате выводит АТС на порт, сопрягаемый с компьютером (часто это com-порт);
- пишем скрипт, разбирающий должным образом получаемый поток байтов с АТС;
- заводим в СУБД (PostgreSQL) необходимые таблицы;
- добавляем к скрипту блоки захвата вывода с АТС и загрузки обработанных скриптом записей в СУБД;
- формируем скрипт сопряжения базы данных с веб-сервером для возможности просмотра данных и запросов абонентами и руководством.
Собственно и все. За пару дней это можно сделать и запустить в эксплуатацию. Например, на следующем комплексе: Pentium-200 MMX, 256 Мб ОЗУ, FreeBSD версии 4.5 и выше с Perl5, PostgreSQL 7 довольно быстро обрабатывается до 100 тыс. записей звонков. Это около 100-150 номеров абонентов, звонящих с интенсивностью выше средней в течение месяца. На Pentium 4 можно легко обрабатывать уже миллионы записей.
Все вышеприведенное будет легко работать и под любым другим UNIX.
Тестирование формата вывода АТС и алгоритм программы
Сначала надо изучить выходные сигналы АТС. Мы рассматриваем NEAX2000 IPS с настройками, практически не отличающимися от стандартных. В нашем случае отличие сводится к тому, что обозначение внутренних номеров начинается со знака *, например, *029. Такая нумерация обычно используется, чтобы можно было звонить «в город» без набора «9».
Соединим кабелем COM-порт компьютера с RS232 разъемом АТС, находящимся на процессорной плате. Для определенности используем нулевой порт – как правило, в компьютере два COM-порта: нулевой и первый.
Обычно тарификация проводится для исходящих вызовов, поэтому здесь мы не будем рассматривать другие звонки.
Введем следующую команду для прослушивания порта, на который поступает информация от АТС (для Linux порт будет называться не dev/cuaa0, а, cкорее всего, /dev/ttyS0):
# cat /dev/cuaa0 >> file
получим при каждом исходящем вызове добавку к содержимому файла file:
^B0!KA050077001*029 08110737260811073756 000050050100 0000 04040 |
(с учетом пробелов для каждой записи получаем 128 байтов, по байту на каждый символ).
Приведенный пример содержит информацию о том, что с номера *029, 11 августа с 7 час. 37 мин. 26 сек до 37 мин. 56 сек. абонент звонил на номер 100 (служба «время» в МГТС).
При разборке файла с помощью Perl в качестве признака конца записи для отдельных вызовов можно принять восклицательный знак «!».
Воспользовавшись вызовом sysread(fd, $v, length), где fd – дескриптор файла $v – переменная, в которую пишется информация, length – количество считывающихся байтов за один вызов, можно установить, что АТС выдает всю запись по вызову отдельными порциями наборов символов, указанных в примере, разделенными пробелами. В АТС имеется буфер (довольно приличный по размеру), в котором хранятся данные по вызовам, так что при соединении с компьютером все предыдущие вызовы «скачиваются» в компьютер.
Код будущей программы состоит из двух блоков:
- блок считывания и обработки (считывает из порта информацию, поступающую из АТС);
- блок загрузки записей в базу данных.
Перед рассмотрением приведенного ниже кода скрипта подчеркнем следующее.
Наша цель – показать, что создать собственную систему биллинга совсем несложно. У каждого системного администратора есть свои излюбленные приемы в Perl, которые он при необходимости использует, приспособив подходящим образом приведенный скрипт или написав свой собственный. Поэтому мы для краткости оставили лишь типовую обработку ошибок, не стремимся усложнять код для обеспечения его безопасности, оптимальности или краткости. Отметим только, что приведенный код неплохо работал у нас на стареньком отдельно стоящем компьютере: процессы внутри АТС относительно медленные и Perl успевает делать все вовремя и без ухищрений.
Отлаженный вариант биллинговой системы с учетом кода для вывода данных с помощью PHP на Apache-сервер достаточно длинный для размещения в тексте, поэтому предоставлен автором всем желающим (www.samag.ru/source).
Реализация программы
Для реализации программы с помощью системы портов FreeBSD помимо PostgreSQL необходимо для работы интерфейса Perl к PostgreSQL установить модули Perl – DBI, DBD-Pg.
Запустив PostgreSQL, создадим таблицу atstarif базы данных ats:
# create table atstarif (nums varchar(15), mydate date, mytime time, min int, numd bigint);
Таким образом, строки в ней будут содержать поля:
- Номер, инициировавший соединение.
- Дата и время начала разговора.
- Длительность звонка в минутах разговора.
- Номер, на который шел вызов.
Программа биллинга на Perl может иметь следующий вид (некоторые комментарии даны в самом коде).
#!/usr/bin/perl
use POSIX qw(:errno_h);
use DBI;
use strict;
my $user="pgsql";
my $dbname="ats";
my $dsh;
my $dbh;
my $p;
my $pid;
$SIG{ALRM}= 'proga';
sub proga {
kill 9 => $pid;
kill 9 => $p;
}
# программа работает в бесконечном цикле, периодически (раз в две минуты) подключаясь к порту, на который
# АТС посылает данные о звонках
while(1)
{
$dsh="dbi:Pg:dbname=$dbname;port=5432";
$dbh=DBI->connect("$dsh", "$user") or die "can't connect: $!\n";
$pid=open (ATS, "cu -l/dev/cuaa1 -s9600|");
$p=($pid)+1;
# программа подключилась к базе данных и начала считывать данные от АТС
eval { alarm(60); };
my @str = <ATS>;
my $sstr;
my $ccc;
my $ss;
my $g;
# в этом цикле начинается обработка поступающих строк от АТС
foreach $sstr (@str) {
if ($sstr=~ /!/)
{
my @s= split /!/, $sstr;
# подсчет времени звонка удобно выделить в отдельную подпрограмму
sub duration {
my $a=substr($ccc, 6, 10);
my $b=substr($ccc, 16, 10);
my @aa=split //, $a;
my @bb=split //, $b;
my $aaa=(($aa[2]*10)+$aa[3])*24*60*60+($aa[4]*10+$aa[5])*60*60+($aa[6]*10+$aa[7])*60+$aa[8]*10+$aa[9];
my $bbb=(($bb[2]*10)+$bb[3])*24*60*60+($bb[4]*10+$bb[5])*60*60+($bb[6]*10+$bb[7])*60+$bb[8]*10+$bb[9];
my $r=$bbb-$aaa;
my $cc1=$r%60;
my $cc11=($r-$cc1)/60;
my $cc2=$cc11%60;
my $cc21=($cc11-$cc2)/60;
my $cc3=$cc21%24;
my $z=" h$cc3 m$cc2 s$cc1 ";
}
foreach $ss (@s)
{
$ss =~ s/.*\*/\*/g;
$ss =~ s/\b0000 .*//g;
$ss =~ s/\b0000600606//g;
$ss =~ s/\b000050050//g;
$ccc=$ss;
my $z=substr($ss, 16, 10);
# теперь применим подпрограмму подсчета времени к переменной $ccc;
my $res=&duration;
$ss=~ s/$z/$res/g;
$ss=~ s/h//g;
$ss=~ s/m//g;
$ss=~ s/s//g;
my @f= split (/\s+/, $ss);
if ($f[4]) {
$f[4] = 1;
}
my $z = $f[2]*60 + $f[3] + $f[4];
my @g = split (//, $f[1]);
if ($g[4]==0) {
$g[4]="";
}
my @gg = ($g[2].$g[3], '.', $g[0].$g[1], '.', '04', " ", $g[4].$g[5], ':', $g[6].$g[7]);
$f[1]= join ("", @gg);
my @ff = ($f[0], $f[1], $z, $f[5]);
$ss = join (" ", @ff);
if ($ss=~ /\*\d\d\d \d\d\.\d\d\.\d\d \d+:\d+ \d+ \d+/) {
# строка сформирована для записи в базу данных
$g= $ss;
# теперь строку $g приведем к необходимому формату и загрузим в базу данных
chomp($g);
my @gg=split / /, $g;
my @ggg=split /\./, $gg[1];
$gg[1]=join "-", "20$ggg[2]", $ggg[1], $ggg[0];
my @dt;
$dt[0]=$dbh->quote("$gg[0]");
$dt[1]=$dbh->quote("$gg[1]");
$dt[2]=$dbh->quote("$gg[2]");
$dt[3]=$dbh->quote("$gg[3]");
$dt[4]=$dbh->quote("$gg[4]");
my $tbl="atstarif ";
$dbh->do("insert into $tbl (nums, mydate, mytime, min, numd)
values ($dt[0], $dt[1], $dt[2], $dt[3], $dt[4])");
}
}
}
}
$dbh->disconnect();
close ATS;
sleep(60);
}
Программу можно запустить в фоновом режиме, обычным образом обеспечить ее запуск при перезагрузке компьютера, организовать ее использование по усмотрению системного администратора.