У меня есть стационарный телефон от Укртелекома. И все бы хорошо, если бы не воровали телефонный кабель. И вот оставшись очередной раз без городской связи, я решил заняться поиском альтернатив. Интернет у меня идет по оптике. Узнав что Укртелеком предоставляет услуги SIP телефонии я уже обрадовался решению проблемы малой кровью. Однако оказалось, что номер при этом у меня изменится. А ведь старый номер это единственное, что меня останавливало от ухода с Укртелекома. Ну раз потери номера мне было не избежать, тогда я решил более плотно заняться вопросом обеспечения себя связью.
Сразу первой же альтернативой видится трубка CDMA, однако у меня в доме 6 трубок заведенные по 3 трубки на 2 базы (4 трубки в доме, 1 в летней кухне, 1 в гараже), это очень удобно, когда телефонная трубка всегда под рукой и не приходится носить ее с собою. Про CDMA терминал Huawei-1201 я тогда не знал, а если бы знал, тогда бы не было сейчас этой статьи. По этому я видел свое решение в мире SIP телефонии.
Выяснил, что для того чтобы подключить аналоговые телефоны к SIP-линии, необходим VOIP шлюз. Ну а раз телефония будет цифровая, то почему бы и не сделать «пульт управления» к ней?
На тот момент, о SIP телефонии я только читал на Хабре заголовки про Asterisk, пропуская их, думая что:
1) это очень сложно для меня.
2) мне это не пригодится.
В обоих пунктах я ошибался.
Когда-то давно я посмотрел видео Дмитрия Бачило, об установке OpenWrt на роутер TP-Link TL-WR842, где он сообщил, между прочего, что устанавливал Asterisk на подобные устройства. Спасибо Дмитрию за его видео. И когда я выбирал себе роутер, то решил что куплю роутер, на который Дмитрий ставил OpenWRT, чтобы не было потом неожиданностью, что в случае покупки другого роутера, он не поддерживает OpenWRT, плюсом было также то, что в нем был USB порт. Установив почти сразу же после покупки OpenWRT 15.05.1 на роутер и вставив в него USB флешку сделав его таким образом его домашней файлопомойкой. Было это около 10-12 месяцев назад. Скажу честно, с OpenWRT я не имел дело до этого и о многих технологиях я узнал благодаря видео Дмитрия.
Также я находил статьи о прикручивании GSM модемов к Asterisk. Поскольку usb порт в роутере у меня имелся, решил осуществить и эту задачу, правда даже не зная с какой стороны начинать ее решать.
Найдя пошаговое руководство о установке Asterisk 11 на OpenWrt я принялся его выполнять, следуя каждому шагу. Однако в конце меня ждало разочарование, при попытке запуска Asterisk 11 в OpenWRT 15 я видел ошибку Segmentation fault. Первый вопрос который возникнет у незнающего читателя: почему именно Asterisk 11? Потому что именно под эту версию есть пакет chan_dongle, который позволяет прикрутить к Астериску GSM модемы Huawei. Поискав в интернете причину Segmentation fault, я не нашел ничего внятного, кроме как «OpenWRT 15 — плохо, OpenWRT 14 — хорошо». По скольку опыта в этом вопросе не было у меня, пришлось «написанному верить». Установив 14-ю версию OpenWRT я все таки смог установить и запустить Астериск. Но ведь этого мало, его нужно еще настроить. Снова поискав в интернете, наткнулся на такого SIP-провайдера как Zadarma, у которого внутренние номера бесплатные. Для теста самое то! Зарегистрировав два аккаунта я попробовал позвонить с компьютера на смартфон. Звонок прошел. Значит я все параметры настроил правильно. На сайте есть инструкция по подключению к Астериск, однако, там не хватает одной строки, которую нужно прописать в файле sip.conf
register => 111111:password@sip.zadarma.com/111111
где 111111-номер телефона
Прописав настройки номера 101 на своем смартфоне я начал снова названивать с компьютера. Наигравшись и убедившись в жизнеспособности идеи я принялся за поиски VOIP шлюза. Перебрав кучу вариантов на OLX я решил все же заказать шлюз в Китае. Заказав Linksys pap2t, я принялся за поиски подходящего GSM модема, все на той же площадке бесплатных объявлений. Самым распространенным модемом поддерживающим передачу голоса был Huawei e1550, его я и купил. Однако по скольку USB хаба на тот момент у меня не было, я пытался установить OpenWRT на карту памяти вставленную в модем. К сожалению безуспешно. Карта памяти инициализировалась после примонтирования корневой файловой системы. Но я не оставлял попыток это сделать и даже решив попробовать полностью пересобрать OpenWRT из исходников (а вдруг поможет?). Неустанно компилируя по вечерам разные версии OpenWRT я пытался добится загрузки с карты памяти модема, но я добился совсем другого результата. Оказывается Asterisk 11 под OpenWRT 15 — работает!!! Если и прошивку и пакет компилировать вместе. Все же сдавшись я приобрел USB хаб, установил OpenWRT на флешку, вставив модем в соседний порт я принялся настраивать chan_dongle. Однако по скольку я уже компилирую прошивку самостоятельно, то и chan_dongle решил собрать из исходников.
В ожидании шлюза из Китая, я решил узнать, что я могу выжать из моей АТС, и первым делом написал на Bash скрипт, который формирует из номера звонящего голосовой файл, и затем при звонке с моего второго номера Zadarma мне проигрывался этот файл и я мог узнать номер последнего звонящего мне человека домой, даже если я находился не дома. Bash скрипт я тоже писал впервые и многое мне было не очевидным. В результате проведя эксперименты, при подключении к медленному каналу интернета и позвонив домой на номер Zadarma многие цифры было не возможно разобрать. Да и Bash скрипт был не лишен ошибок, по этому его я тут выкладывать не буду, скажу лишь, что пока пытался разобраться с вызовом скрипта наткнулся на такую замечательную вещь как AGI. Которая позволяет использовать в качестве обработчика PHP, Perl или CGI сценарии.
Параллельно написанию скриптов я названивал своим друзьям и знакомым через разные софтфоны со смартфона. Маршрут звонка был следующий: SoftFon (разные SIP клиенты) => Asterisk => GSM modem => GSM телефон. И все без исключения мне говорили о ужасном качестве моего голоса, при этом я всех слышал прекрасно. Это подвело меня к тому, что на каждый канал мне пришлось прописать разрешенные кодеки. Попробовав кодек G722, я понял, что мощностей моего роутера явно будет недостаточно для кодирования голоса и работы в качестве основного шлюза моего интернета. По этому для Астериска я решил приобрести отдельный роутер. Однако самыми оптимальными по качеству звука выявились кодеки G711(alaw/ulaw). На них и остановился, хотя качеством своего голоса я был явно недоволен.
Следующим шагом для меня стала детализация звонков, и их запись. Обе эти задачи можно решить внутренними средствами Астериска, 1 — CDR, 2 — MixMonitor. CDR — служит для детализации звонков, и записывать их может от CSV файла до базы данных MySQL, которую я и планировал использовать в качестве БД. Но позже вычитав, что MySQL для роутера будет слишком прожорливая, я сместил свой выбор в пользу SqLite3. Вот так выглядит мой файл cdr_sqlite3_custom.conf
table => cdr
columns => calldate, clid, dcontext, channel, dstchannel, lastapp, lastdata, duration, billsec, disposition, amaflags, accountcode, uniqueid, userfield, call_num, dist
values => '${CDR(start)}','${CDR(clid)}','${CDR(dcontext)}','${CDR(channel)}','${CDR(dstchannel)}','${CDR(lastapp)}','${CDR(lastdata)}','${CDR(duration)}','${CDR(billsec)}','${CDR(disposition)}','${CDR(amaflags)}','${CDR(accountcode)}','${CDR(uniqueid)}','${CDR(userfield)}','${CDR(call_num)}','${CDR(dist)}'
А обернув вызов в MixMonitor я делаю запись звонка, но запись идет в wav файл, а этот формат любит много места, по этому я установил пакет lame и после записи, я конвертирую файл в mp3.
Тем временем мне уже приехал мой Linksys pap2t и я уже мог подключить вместо смартфона для теста обычный стационарный телефон. И каково же было мое удивление, когда при звонке через этот шлюз, пропала проблема с качеством исходящего голоса. Т.е. меня уже слышали также хорошо, как и я слышал собеседника.
В поисках роутера который будет выделен только под АТС (искать решил на Али), мой выбор пал на OYE-0001, потому как в нем уже имелся встроенный кард ридер для карточек MicroSD и OpenWRT стояла из коробки. Сделав заказ снова сел за написание скриптов. Пока писал скрипты, выяснилось, что модем Huawei E1550 имеют свойство уходит в астрал, и помогает от этого только полный сброс по питанию. Т.е. вытаскивание и установка модема обратно. (Забегая вперед скажу, эта проблема у меня пока так и не решена, хотя предполагаемое решение имеется.)
Получив китайский роутер снова начал пытаться установить прошивку на внешний носитель, но на этот раз на карту памяти, вставленную в кард-ридер. На стоковой прошивке у меня получилось это сделать, однако на ней не работали необходимые мне пакеты из репозитория OpenWRT. По этому попытался снова скомпилировать прошивку самостоятельно. Пробовал собирать именно 15-ю версию. Но перепробовав разные преднастройки от подобных роутеров, на процессоре MT7620, у меня постоянно что-то, да не работало так как надо. Но поскольку опыта мало, то и решить проблему я не мог. Однако выяснилось, что 16-я (trunk, тестовая) версия OpenWRT поддерживает мой роутер из коробки и на сайте есть даже прошивка, которую я поспешил загрузить. И в данной прошивке мне удалось сделать все что я задумывал, т.е. установить OpenWRT на карту памяти, настроить сеть и установить нужные мне пакеты. Если кто будет ставить себе 16, транковую, версию OpenWRT, в ней отсутствует пакет Luci. Т.е. графический Web-интерфейс. И настройки сети необходимо будет вносить через консоль в файл /etc/config/network.
Заключительный этап — подключение городского номера.
В качестве провайдера для городского номера я выбрал себе Интертелеком. Это CDMA оператор, который предоставляет услуги SIP телефонии. Я себе видел свою домашнюю АТС следующим образом: 2-GSM модема и SIP канал от Интертелекома, т.к. chan_dongle не умел работать с CDMA модемами. Но снова собирая информацию я наткнулся на chan_cdma. Эта библиотека позволяет работать с CDMA модемом ZTE AC8710. Спасибо огромное Олегу Жабко, за написание модуля для работы с этим модемом. Но она написана под Астериск 1.8, а у меня использовался 11. Далее среди объявлений я наткнулся на модем ZTE AC8700 (он внешне 1 в 1 АС8710), за 35 грн. (это шара). По этому решил вложится в этот эксперимент и приобрел данный модем. Позже выяснил, что chan_cdma написан на основе chan_dongle. По этому для того, чтобы заставить работать модем под 11 Астериск, потребуется нудная копипаста кода с одного проекта в другой. Но проделав тщательнейшую копипасту, перенося в проект chan_dongle только участки отвечающие за работу с CDMA, но особо в смысл кода я не вникал. Завести работать модем и позвонить оператору мне увы не удалось. Тогда пришлось приложить усилий и все же начал вникать в код постоянно вставляя отладочные сообщения. Начал замечать, что в коде есть места проверки данных, которые закоментированы, а вместо них вставлены заглушки «if(1) {...}». Тогда я подобрался к коду который отвечает за проверку регистрации в сети и тоже его также «заглушил». И «о чудо!», я смог сделать вызов оператору, но поскольку назначения данных проверок я не знаю, то я лишь добавил в «пропускающие» число которое возвращал мой модем.
С библиотекой chan_cdma я мучался около 2-х недель, и за это время как раз наткнулся на CDMA терминал Huawei-1201 и поскольку в оптимистичный финал с модемом я не верил. Тогда я решил точно не зависеть от проводов, оптический кабель тоже ведь могут украсть. По этому решил организовать «план Б». Приобрести Voip шлюз с FXO портом, приобрести Huawei-1201, посадить на этот порт терминал и это все будет идти через мой Астериск сервер. Снова посмотрел объявления на сайте я наткнулся на TP-Link TD-VG3631. Почитав в интернете описания, я понял, что это именно то, что мне нужно. 2FXS порта и 1 FXO. Да еще и в моем городе продается, я незамедлительно решил купить. Но после покупки, покопавшись в настройках я был сильно разочарован. Т.к. во-первых звонить на «FXO» можно было только со внутренних телефонов, а во вторых, для этого нужно было использовать «префикс». А это уже «костыль» в моей системе, т.к. тогда звонок будет идти в обход моей АТС и не все дома запомнят, что для звонка «теперь нужно набирать дополнительные цифры». По этому до покупки терминала дело так и не дошло. Еще одним разочарованием стало то, что Tp-Link TD-VG3631 не признавал SIP номера, короче 3-х цифр. До этого мои внутренние номера были 11 и 22 соответственно. К тому моменту мне уже отремонтировали линию Укртелекома и по этому пока я решил оставить Tp-Link TD-VG3631, благо входящий вызов на FXO порт он мог перенаправлять на SIP номер.
К тому моменту у меня уже сформировался AGI скрипт обработки входящих звонков, алгоритм его работы был следующий. Если номер звонит впервые, тогда проверяем пропускаем звонок в любом случае и записываем номер в телефонную книгу, если же этот номер есть у нас в телефонной книге и он не в черном списке — пропускаем звонок, иначе сбрасываем. Файл БД телефонной книги решил разместить там же где и находится файл детализации звонков в /var/log/asterisk. Но тут снова ждало разочарование. После перезагрузки эта папка очищается. Пришлось в исходниках Астериска в файле cdr_sqlite3_custom.c изменить путь к расположению файла master.db на /www/. Также скажу, что пакет res_agi.ipk из коробки не работает и для работы требует res_speech.so. Тут я попробовал вручную подсунуть этот файл из папки сборки OpenWRT в каталог /var/lib/asterisk/modules и все заработало.
Следующим изменением файла обработки входящих звонков, номер проверялся и если он соответствовал моему второму задармашнему номеру, тогда первым делом на него высылался список из последних 5 входящих, с временем когда они звонили, а лишь затем шел звонок на внутренние номера. Вот собственно сам код.
#!/usr/bin/php-cgi -q
<?php
class MyDB extends SQLite3
{
function __construct($filename)
{
$this->path = "/www/";
$this->filename = $filename;
if(!file_exists($this->path.$filename) && $filename == 'phonebook.db'){
$this->create_phonebook_db();
}else{
$this->open($this->path.$filename);
}
}
public function doit($sql)
{
$result = $this->query($sql);
$row = array();
$i=0;
while($res = $result->fetchArray(SQLITE3_ASSOC)){
$row[$i] = $res;
$i++;
}
return $row;
}
private function create_phonebook_db()
{
$this->open($this->path.'phonebook.db');
$sql="CREATE TABLE phonebook(
id INTEGER PRIMARY KEY AUTOINCREMENT,
phone_num TEXT,
name TEXT,
blacklist INTEGER,
last_call DATATIME
)";
$this->query($sql);
$sql="CREATE TABLE sms(
id INTEGER PRIMARY KEY AUTOINCREMENT,
phone_num TEXT,
sms_text TEXT,
sms_time DATATIME
)";
$this->query($sql);
$sql="CREATE TABLE ussd(
id INTEGER PRIMARY KEY AUTOINCREMENT,
phone_num TEXT,
sms_text TEXT,
sms_time DATATIME
)";
$this->query($sql);
}
}
function show_date_from_base($date)
{
$time = strtotime($date);
$time+=7200;
return date('d.m.Y в H:i:s',$time);
}
set_time_limit(0);
require('phpagi.php'); # специальная библиотека для удобства работы с AGI
$agi = new AGI();
$db = new MyDB('phonebook.db');
$now = date('Y-m-d H:i:s');
$cid = $agi->request['agi_callerid'];
if(substr($cid,0,1) == "+") $cid = substr($cid,1); //убираем +
if(strlen($cid) > 10 && substr($cid,0,3) == "380") $cid = substr($cid,2); //заменяем 380ХХ на 0ХХ
$agi->set_variable("CDR(call_num)", $cid);
if($cid == "XXXXXX"){ //Zadarma
$db_cdr = new MyDB('master.db');
$sql="select * from cdr where call_num != 'sms_ussd' AND channel NOT LIKE '%ussd%' AND dist = 'in' GROUP BY call_num ORDER BY calldate DESC LIMIT 0,5";
$calls = $db_cdr->doit($sql);
$agi->answer();
$agi->send_text("_____________n");
$agi->send_text("_____________n");
for($n = 0; $n < count($calls);$n++){
$agi->send_text($calls[$n]['call_num']."n");
$agi->send_text("звонил ".show_date_from_base($calls[$n]['calldate'])."n");
}
$agi->wait_for_digit(10000);
}
$sql="select * from phonebook where phone_num = '".$cid."'";
$book = $db->doit($sql);
if(count($book) == 0)
{
$agi->exec_dial("SIP/221&SIP/222&SIP/33"); # ну и собственно звонок
$agi->hangup(); # конец звонка
if(trim($cid)!=""){
$sql="insert into phonebook( phone_num, name, blacklist, last_call) VALUES ('".$cid."', '', '0', '".$now."')";
$db->exec($sql);
}
}else{
if($book[0]['blacklist'] == 0)
{
$agi->exec_dial("SIP/221&SIP/222&SIP/33"); # ну и собственно звонок
$agi->hangup(); # конец звонка
if(trim($cid)!=""){
$sql="UPDATE phonebook SET last_call = '".$now."' where phone_num = '".$cid."'";
$db->exec($sql);
}
}else{
$agi->hangup(); # конец звонка
if(trim($cid)!=""){
$sql="UPDATE phonebook SET last_call = '".$now."' where phone_num = '".$cid."'";
$db->exec($sql);
}
}
}
?>
RECDIR=/var/spool/asterisk/monitor
SAVEMP3DIR=/www/light/monitor
[dongle-incoming-sms]
exten => sms,1,Noop(Incoming SMS from ${CALLERID(num)} ${BASE64_DECODE(${SMS_BASE64})} — ${SMS})
exten => sms,n, Set(CDR(call_num)=sms_ussd)
exten => sms,n,AGI(sms.php, ${CALLERID(num)}, ${SMS})
exten => sms,n,Hangup()
[dongle-incoming-ussd]
exten => ussd,1,Noop(Incoming USSD: ${BASE64_DECODE(${USSD_BASE64})})
exten => ussd,n, Set(CDR(call_num)=sms_ussd)
exten => ussd,n,AGI(ussd.php, ussd, ${USSD})
exten => ussd,n,Hangup()
[all-in]
include => dongle-incoming-sms
include => dongle-incoming-ussd
exten => X., 1, Set(MON_FILE=${UNIQUEID}-${STRFTIME(${EPOCH},,%Y-%m-%d-%H%M)}-${CALLERID(num)}-${CDR(dst)})
exten => _X., 2, Set(CDR(userfield)=${MON_FILE});
exten => _X., 3, Set(CDR(call_num)=${CALLERID(num)})
exten => _X., 4, Set(CDR(dist)=in)
exten => _X., 5, MixMonitor(${MON_FILE}.wav)
exten => _X., 6, AGI(in.php); направлены на внутренний номер
exten => _X., 7, Hangup()
exten => _X., 8, StopMixMonitor()
exten => +X., 1, Set(MON_FILE=${UNIQUEID}-${STRFTIME(${EPOCH},,%Y-%m-%d-%H%M)}-${CALLERID(num)}-${CDR(dst)})
exten => _+X., 2, Set(CDR(userfield)=${MON_FILE});
exten => _+X., 3, Set(CDR(call_num)=${CALLERID(num)})
exten => _+X., 4, Set(CDR(dist)=in)
exten => _+X., 5, MixMonitor(${MON_FILE}.wav)
exten => _+X., 6, AGI(in.php); направлены на внутренний номер
exten => _+X., 7, Hangup()
exten => _+X., 8, StopMixMonitor()
exten => h,1,System(/usr/bin/lame -b 16 -silent ${RECDIR}/${MON_FILE}.wav ${SAVEMP3DIR}/${MON_FILE}.mp3 > /var/log/asterisk/wav_2_mp3.log)
exten => h,n,System(/bin/rm -r ${RECDIR}/${MON_FILE}.wav)
Ну а для вывода статистики, по звонкам набросал на скорую руку Web-интерфейс. Вот так вот он выглядит. И положил его в папку /www/light/.
→ Ссылка на гитхаб веб интерфейса статистики
→ Ссылка на гитхаб chan_dongle с поддержкой CDMA модемов ZTE AC8700/8710.
Makefile для компиляции в последней версии OpenWRT (необходимо заменить родной Makefile в папке /package/feeds/telephony/asterisk-11.x-chan-dongle, все патчи оставить как есть).
# Copyright © 2013 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=asterisk11-chan-dongle
PKG_VERSION:=1.1r35
PKG_REV:=ff0a798b8f73b71af328c53862fd33ec5fe9ce85
PKG_RELEASE:=6
PKG_SOURCE_SUBDIR:=asterisk11-chan-dongle-$(PKG_VERSION)
PKG_SOURCE:=$(PKG_SOURCE_SUBDIR).tar.gz
PKG_SOURCE_URL:=https://github.com/superl3n1n/asterisk-chan-cdma-gsm-dongle.git
PKG_SOURCE_PROTO:=git
PKG_SOURCE_VERSION:=$(PKG_REV)
PKG_BUILD_DIR=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR)
PKG_FIXUP:=autoreconf
PKG_LICENSE:=GPL-2.0
PKG_LICENSE_FILES:=COPYRIGHT.txt LICENSE.txt
PKG_MAINTAINER:=Jiri Slachta <jiri@slachta.eu>
include $(INCLUDE_DIR)/package.mk
define Package/asterisk11-chan-dongle
SUBMENU:=Telephony
SECTION:=net
CATEGORY:=Network
URL:=https://code.google.com/p/asterisk-chan-dongle/
DEPENDS:= asterisk11 +libiconv-full +kmod-usb-acm +kmod-usb-serial +kmod-usb-serial-option +libusb-1.0 +usb-modeswitch
TITLE:=Huawei UMTS 3G dongle support
endef
define Package/asterisk11-chan-dongle/description
Asterisk channel driver for Huawei UMTS 3G dongle.
endef
MAKE_ARGS:=
CC="$(TARGET_CC)"
LD="$(TARGET_CC)"
CFLAGS="$(TARGET_CFLAGS) -DASTERISK_VERSION_NUM=110000 -DLOW_MEMORY -D_GNU_SOURCE -D_XOPEN_SOURCE=600 $(TARGET_CPPFLAGS) -I$(STAGING_DIR)/usr/lib/libiconv-full/include -I$(STAGING_DIR)/usr/include/asterisk-11/include -DHAVE_CONFIG_H -I. -fPIC"
LDFLAGS="$(TARGET_LDFLAGS) -L$(STAGING_DIR)/usr/lib/libiconv-full/lib -liconv"
DESTDIR="$(PKG_INSTALL_DIR)/usr/lib/asterisk/modules"
CONFIGURE_VARS +=
ac_cv_type_size_t=yes
ac_cv_type_ssize_t=yes
define Build/Configure
$(call Build/Configure/Default,
--with-asterisk=$(STAGING_DIR)/usr/include/asterisk-11/include
$(MAKE_ARGS)
)
endef
define Build/Compile
mkdir -p $(PKG_INSTALL_DIR)/usr/lib/asterisk/modules
$(MAKE) -C "$(PKG_BUILD_DIR)" $(MAKE_ARGS) all install
endef
define Package/asterisk11-chan-dongle/conffiles
/etc/asterisk/dongle.conf
endef
define Package/asterisk11-chan-dongle/install
$(INSTALL_DIR) $(1)/etc/asterisk
$(INSTALL_DATA) $(PKG_BUILD_DIR)/etc/dongle.conf $(1)/etc/asterisk/
$(INSTALL_DIR) $(1)/usr/lib/asterisk/modules
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/lib/asterisk/modules/chan_dongle.so $(1)/usr/lib/asterisk/modules/
endef
$(eval $(call BuildPackage,asterisk11-chan-dongle))
Автор: Superl3n1n