Действительно полезное приложение для Digium телефонов
Приветствую, хабрасообщество.
Чуть более года назад мы разрабатывали приложения для Digium телефонов. Несмотря на то, что планы были обширными, мы остановились только на следующих вариациях:
- Погода с сайта гисметео
- Курс валют с сайта центробанка
- RSS лента с новостных порталов
Данные приложения были написаны, чтобы ознакомить сообщество с API и примерами, даже больше just for fun. Cофт, если так можно его назвать, не несет себе никакого уникального применения, которое было бы полезно реальному бизнесу.
Сегодня мы решили вернуться к этой теме, и поделиться другим, на наш взгляд намного более интересным приложением, которое отображает вызов на экране телефона, если пользователи находятся в одной пикап группе и позволяет его перехватить.
За подробностями — > хабракат
Ввиду того, что телефон сам не может обратиться к астериску по HTTP, была выбрана структура клиент-сервер. Сервер на питоне, который «ловит» от астериска CURL запросы при звонке, и javascript на телефоне, который с некоторой периодичностью опрашивает о наличии новых записей.
from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer
import urlparse
import json
import shelve
import datetime
import sys
import os
TIME_FORMAT = '%d.%m.%Y %H:%M:%S.%f'
DB_NAME = "db.db"
class HttpProcessor(BaseHTTPRequestHandler):
def do_GET(self):
if "/get" in self.path:
fields = self.get_fields()
if not fields:
print "There are no parameters"
return
callgroup = fields.get("callgroup")
pickupgroup = fields.get("pickupgroup")
if callgroup is None or pickupgroup is None:
print "There are no pickupgroup or no callgroup"
self.send_error(404)
return None
db = shelve.open( DB_NAME)
data = []
for key in db.keys():
if "callgroup" in db[key] and "pickupgroup" in db[key] and str(db[key]["callgroup"]) == str(callgroup) and str(db[key]["pickupgroup"]) == str(pickupgroup):
entry = db[key]
data.append(entry)
# Make simple dict for json
self.send_json(data)
db.close()
def do_POST(self):
data = self.get_json_data()
if "uid" not in data:
print "There are no a number"
return
uid = str(data["uid"])
print data
if "/put" in self.path:
db = shelve.open( DB_NAME)
db[uid] = data
db.close()
data = {"status": 200, "message": "OK"}
self.send_json(data)
elif "/del" in self.path:
db = shelve.open( DB_NAME)
if uid in db:
del db[uid]
data = {"status": 200, "message": "OK"}
self.send_json(data)
def send_json(self, data):
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(data))
def get_json_data(self):
length = int(self.headers.getheader('content-length'))
field_data = self.rfile.read(length)
try:
data = json.loads(field_data)
except:
print "JSON error:" + field_data
self.send_error(404)
return None
return data
def get_fields(self):
fields_data = self.path.split("?")
if len(fields_data) < 2:
return
GET = {}
args = fields_data[1].split('&')
for arg in args:
t = arg.split('=')
if len(t) > 1:
k, v = arg.split('=')
GET[k] = v
return GET
if __name__ == "__main__":
if os.path.exists( DB_NAME):
os.remove( DB_NAME)
host = "192.168.1.254"
port = 8000
serv = HTTPServer((host, port), HttpProcessor)
print "Server running at {}:{}".format(host, port)
serv.serve_forever()
Не забудьте дать этому файлу права на исполнение (chmod +x), запустить и добавить в автозапуск.
Установка приложения на телефон
Как вы наверняка знаете, или слышали, Digium телефон можно настроить с помощью веб-интерфейса, либо с помощью некоего проприетарного провиженинга DPMA. (Digium Phone Module Asterisk).
Если у вас самая простая настройка (через веб), то вам необходимо перейти в меню телефона System tools и нажать Enable App Development, затем залогиниться на телефон по адресу phone_ip-address/app_dev (по дефолту: Юзер: admin, Пароль: 789) и там нажать большую зеленую кнопку Add App.
Вот собственно файл на скачивание самого приложения: pbxware.ru
var incomingGroupCall = {}
var screen = require('screen');
//util for debugging
var util = require('util');
var app = require('app');
//we needs to get all info about app
app.init();
screen.clear();
//Get config of app(we needs settings)
var config = app.getConfig();
var callgroup = config.settings.callgroup; //Get callgroup
var pickupgroup = config.settings.pickupgroup; //Get pickupgroup
var server = config.settings.server; //server uri, like http://{host}:{port}
var app_name = config.settings.id; //App name from json file
var phonePrefix = config.settings.prefix; //App name from json file
var language = config.settings.language || "ru";
var uids = []; // This list contains all uids server give us at runtime
var phonesCount = 0; // This variable needs to watch uids missing from uids list
var timer;
var currentListPos = 0;
var listWidget = new List(0, 0, window.w, window.h);
var lang = digium.readFile("app", language + ".json");
language = JSON.parse(lang);
incomingGroupCall.show = function () {
util.debug("Call show");
if (this.visible) {
window.add(listWidget);
}
this.update();
if (timer) {
clearInterval(timer);
}
timer = setInterval(this.update, 1100);
};
incomingGroupCall.showGui = function(message, params) {
util.debug("Show gui");
var lastSelected = listWidget.selected;
listWidget.clear();
listWidget.set(0,0, message);
var i=1;
if(!params){
return;
}
params.forEach(function(entry) {
var msg = entry.from + " --> " + entry.to;
if(i<=9){
msg = "[" + i + "] " + msg;
} else if(i === 10) {
msg = "[0] " + msg;
} else if(i === 11) {
msg = "[*] " + msg;
} else if(i === 12) {
msg = "[#] " + msg;
}
listWidget.set(i, 0, msg);
listWidget.set(i, 1, entry.to); //container to get value in key handler
i++;
});
listWidget.select(lastSelected);
}
incomingGroupCall.update = function() {
var request = new NetRequest();
request.open("GET", server + "/get?callgroup="+callgroup+"&pickupgroup="+pickupgroup);
request.onreadystatechange = function() {
//(readyState === 4) indicates a completed request
if (4 === request.readyState) {
if (200 === request.status) {
try {
var data = JSON.parse(request.responseText);
if (!data || data.length === 0) {
if (!digium.app.inForeground) {
return;
}
incomingGroupCall.showGui(language["NO_CALLS"]);
return;
}
//Remove ended calls
var currentUids = data.map( function(item) {
return item.uid;
});
var needsToRefresh = false;
var newEntryAvailable = false;
uids.forEach(function(uid) {
if(currentUids.indexOf(uid) === -1) {
uids.splice(uids.indexOf(uid), 1);
needsToRefresh = true;
}
});
// Add new phones to list
data.forEach(function(entry) {
if (uids.indexOf(entry.uid) === -1) {
needsToRefresh = true;
newEntryAvailable = true;
uids.push(entry.uid);
}
});
util.debug("New entry:" + newEntryAvailable);
if (!digium.app.inForeground && newEntryAvailable) {
digium.foreground();
}
if(needsToRefresh) {
incomingGroupCall.showGui(language["INCOMING_CALLS"], data);
}
} catch (e) {
util.debug('request error: ' + JSON.stringify(e));
incomingGroupCall.showGui(language["SERVER_UNAVAILABLE"]);
}
} else {
util.debug('request error1: ' + request.status);
incomingGroupCall.showGui(language["SERVER_UNAVAILABLE"]);
}
}
}.bind(this);
request.setTimeout(1000);
request.send();
};
//initialize variables
incomingGroupCall.init = function () {
this.widgets = {};
//stay open when the app is backgrounded
digium.app.exitAfterBackground = false;
this.visible = digium.app.inForeground;
incomingGroupCall.listeners();
// setInterval(digium.restart, 180000);
};
incomingGroupCall.listeners = function () {
//show the full window when the app is foregrounded
digium.event.observe({
'eventName' : 'digium.app.foreground',
'callback' : function () {
util.debug("app.foregrounded");
window.clear();
this.visible = digium.app.inForeground;
this.setButtons();
this.show();
}.bind(this)
});
//show the idle window when the idleScreen is shown
digium.event.observe({
'eventName' : 'digium.app.background',
'callback' : function () {
util.debug("app.background");
this.visible = digium.app.inForeground;
window.clearSoftkeys();
this.show();
}.bind(this)
});
};
incomingGroupCall.setButtons = function () {
window.onkeyselect = function() {
var phone = listWidget.get(listWidget.selected, 1);
util.debug("Selected " + phone);
digium.phone.dial({
"number": phonePrefix+phone
})
}
window.onkey = function(e) {
//Digits keyboard handler
try {
var key = e.key;
if(e.key == "0") {
key = 10;
} else if (e.key == "*") {
key = 11;
} else if (e.key == "#") {
key = 12;
}
var phone = listWidget.get(key, 1);
if (phone) {
digium.phone.dial({
"number": phonePrefix+phone
})
}
} catch(e) {
util.debug("Error in trying to dial");
}
util.debug(JSON.stringify(e));
}
window.setSoftkey(4, language['EXIT'], function() {
digium.app.exitAfterBackground = true;
digium.background();
}.bind(this));
window.setSoftkey(3, language['HIDE'], function() {
digium.background();
}.bind(this));
}
incomingGroupCall.init();
incomingGroupCall.show();
В настройках нового приложения необходимо указать следующие опции:
callgroup: 1
pickupgroup: 1
server: 192.168.1.254:8000 (ну или любой другой IP, где запущен питоновский скрипт)
prefix: *8 (префик для перехвата звонка)
language: ru (поддерживается en / ru)
Если вы используете DPMA, то необходимо загрузить приложение на телефоны с помощью этой инструкции
Запускаете приложение. Можете оставить открытым, либо свернуть в фон.
Изменение диалплана Asterisk
Итак, приложение мы записали на телефон, ретранслятор на сервере тоже запустили. Осталось внести изменения в диалплан астериска, чтобы он сообщал, что на добавочный номер пришел звонок с нужными нам параметрами callgroup и PickupGroup.
Например, вы можете использовать наш рабочий диалплан
exten => _7XX,1, NoOp (Call from ${CALLERID (num)} to ${EXTEN})
same => n, Set (CallGroup=${SIPPEER (${EXTEN}, callgroup)})
same => n, NoOp (Callgroup = ${CallGroup})
same => n, Set (PickupGroup=${SIPPEER (${EXTEN}, pickupgroup)})
same => n, NoOp (PickupGroup = ${PickupGroup})
same => n, System (curl -i -H «Accept: application/json» -H «Content-Type: application/json» -X POST -d '{«uid»:»${UNIQUEID}», «callgroup»:»${CallGroup}», «pickupgroup»:»${PickupGroup}», «from»:»${CALLERID (num)}», «to»:»${EXTEN}»}' 192.168.1.254:8000/put)
same => n, Dial (SIP/${EXTEN},60, Tt)
same => n, Set (CallGroup=${SIPPEER (${EXTEN}, callgroup)})
same => n, Hangup ()
exten => h,1, NoOp (END of App)
same => n, System (curl -i -H «Accept: application/json» -H «Content-Type: application/json» -X POST -d '{«uid»:»${UNIQUEID}», «callgroup»:»${CallGroup}», «pickupgroup»:»${PickupGroup}», «from»:»${CALLERID (num)}», «to»:»${TARGETNO}»}' 192.168.1.254:8000/del)
same => n, Hangup ()
Приложение в действии
При входящем звонке на экране телефона всплывает наше приложение, которое показывает, звонки в данной колл-группе и позволяет его перехватить. Никакой другой сигнализации нет (например мелодии или световой индикации)
По номерам можно перемещаться с помощью кнопок «вверх» и «вниз», можно перехватить вызов нажатием на кнопку «ОК», тогда перехватится выделенный номер, либо с помощью цифр и *, #.
Запускаешь приложение, сворачиваешь его и ждешь, когда позвонят). При новом звонке приложение развернется. Можно опять свернуть его.