Приветствую, хабрасообщество.
Чуть более года назад мы разрабатывали приложения для Digium телефонов. Несмотря на то, что планы были обширными, мы остановились только на следующих вариациях:
- Погода с сайта гисметео
- Курс валют с сайта центробанка
- RSS лента с новостных порталов
Данные приложения были написаны, чтобы ознакомить сообщество с API и примерами, даже больше just for fun. Cофт, если так можно его назвать, не несет себе никакого уникального применения, которое было бы полезно реальному бизнесу.
Сегодня мы решили вернуться к этой теме, и поделиться другим, на наш взгляд намного более интересным приложением, которое отображает вызов на экране телефона, если пользователи находятся в одной пикап группе и позволяет его перехватить.
За подробностями — > хабракат
Ввиду того, что телефон сам не может обратиться к астериску по HTTP, была выбрана структура клиент-сервер. Сервер на питоне, который «ловит» от астериска CURL запросы при звонке, и javascript на телефоне, который с некоторой периодичностью опрашивает о наличии новых записей.
server.py
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()
Приложение в действии
При входящем звонке на экране телефона всплывает наше приложение, которое показывает, звонки в данной колл-группе и позволяет его перехватить. Никакой другой сигнализации нет (например мелодии или световой индикации)
По номерам можно перемещаться с помощью кнопок «вверх» и «вниз», можно перехватить вызов нажатием на кнопку «ОК», тогда перехватится выделенный номер, либо с помощью цифр и *, #.
Запускаешь приложение, сворачиваешь его и ждешь, когда позвонят). При новом звонке приложение развернется. Можно опять свернуть его.
Zenitchik
Гисметео — не лучший выбор. Лучше данными с сайта гидрометцентра.