|
|
@ -1,7 +1,7 @@ |
|
|
|
|
|
|
|
import linphone |
|
|
|
import time |
|
|
|
import time |
|
|
|
import threading |
|
|
|
import threading |
|
|
|
import subprocess |
|
|
|
import subprocess |
|
|
|
from pylinphone.pylinphone import LinphoneCommunicationSocket |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PhoneProxyConfiguration(object): |
|
|
|
class PhoneProxyConfiguration(object): |
|
|
@ -40,27 +40,46 @@ class PhoneEvent(object): |
|
|
|
|
|
|
|
|
|
|
|
@classmethod |
|
|
|
@classmethod |
|
|
|
def string(cls, val): |
|
|
|
def string(cls, val): |
|
|
|
for k, v in vars(cls).items(): |
|
|
|
for k, v in vars(cls).iteritems(): |
|
|
|
if v == val: |
|
|
|
if v == val: |
|
|
|
return k |
|
|
|
return k |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PhoneInterface(object): |
|
|
|
class PhoneInterface(object): |
|
|
|
def __init__(self, config): |
|
|
|
def __init__(self, config): |
|
|
|
|
|
|
|
cbs = { |
|
|
|
|
|
|
|
'global_state_changed': self.__global_state_changed, |
|
|
|
|
|
|
|
'registration_state_changed': self.__registration_state_changed, |
|
|
|
|
|
|
|
'call_state_changed': self.__call_state_changed |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
self.__event_cbs = [] |
|
|
|
self.__event_cbs = [] |
|
|
|
|
|
|
|
|
|
|
|
self.__config = config |
|
|
|
self.__config = config |
|
|
|
self.__core = LinphoneCommunicationSocket("/var/tmp/debian-sid/tmp/linphone") |
|
|
|
self.__core = linphone.Core.new(cbs, None, config.linphone_config) |
|
|
|
|
|
|
|
|
|
|
|
self.__core.onLinphoneCallIncomingReceived = self.on_LinphoneCallIncomingReceived |
|
|
|
|
|
|
|
self.__core.onLinphoneCallOutgoingRinging = self.on_LinphoneCallOutgoingRinging |
|
|
|
|
|
|
|
self.__core.onLinphoneCallConnected = self.on_LinphoneCallConnected |
|
|
|
|
|
|
|
self.__core.onLinphoneCallEnd = self.on_LinphoneCallEnd |
|
|
|
|
|
|
|
self.__core.onLinphoneCallError = self.on_LinphoneCallError |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Create and add all proxy configs |
|
|
|
# Create and add all proxy configs |
|
|
|
for p in config.proxies: |
|
|
|
for p in config.proxies: |
|
|
|
aid = self.__core.register(p.identity, p.proxy, p.password, p.username) # sip:XXXX@hg.eventphone.de, hg.eventphone.de, MySecretPassword, XXXX |
|
|
|
ainfo = self.__core.create_auth_info(p.username, p.username, |
|
|
|
|
|
|
|
p.password, None, p.realm, |
|
|
|
|
|
|
|
None) |
|
|
|
|
|
|
|
self.__core.add_auth_info(ainfo) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pconf = self.__core.create_proxy_config() |
|
|
|
|
|
|
|
pconf.edit() |
|
|
|
|
|
|
|
if self.__core.version < '3.9.0': |
|
|
|
|
|
|
|
pconf.identity = p.identity |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
pconf.identity_address = pconf.normalize_sip_uri(p.identity) |
|
|
|
|
|
|
|
pconf.publish_enabled = False |
|
|
|
|
|
|
|
pconf.realm = p.realm |
|
|
|
|
|
|
|
pconf.register_enabled = True |
|
|
|
|
|
|
|
pconf.server_addr = p.proxy |
|
|
|
|
|
|
|
self.__core.add_proxy_config(pconf) |
|
|
|
|
|
|
|
pconf.done() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if p.name == config.default_proxy: |
|
|
|
|
|
|
|
self.__core.default_proxy_config = pconf |
|
|
|
|
|
|
|
|
|
|
|
self.__audioproc = None |
|
|
|
self.__audioproc = None |
|
|
|
aplay = subprocess.Popen(['aplay', '-qD%s' % config.sound_device], |
|
|
|
aplay = subprocess.Popen(['aplay', '-qD%s' % config.sound_device], |
|
|
@ -70,44 +89,68 @@ class PhoneInterface(object): |
|
|
|
stdout=aplay.stdin) |
|
|
|
stdout=aplay.stdin) |
|
|
|
|
|
|
|
|
|
|
|
# Set default parameters overriding the ones from the given config file |
|
|
|
# Set default parameters overriding the ones from the given config file |
|
|
|
# TODO: figure out how to set at least some of these settings through the unix socket |
|
|
|
self.__core.set_user_agent('FeTAp 615', '0.1') |
|
|
|
#self.__core.set_user_agent('FeTAp 615', '0.1') |
|
|
|
self.__core.stun_server = config.stun_server |
|
|
|
#self.__core.stun_server = config.stun_server |
|
|
|
self.__core.ringback = '' |
|
|
|
#self.__core.ringback = '' |
|
|
|
self.__core.max_calls = 1 |
|
|
|
#self.__core.max_calls = 1 |
|
|
|
self.__core.inc_timeout = config.incoming_timeout |
|
|
|
#self.__core.inc_timeout = config.incoming_timeout |
|
|
|
self.__core.set_call_error_tone(linphone.Reason.Busy, '') |
|
|
|
#self.__core.set_call_error_tone(linphone.Reason.Busy, '') |
|
|
|
self.__core.disable_chat(linphone.Reason.None) |
|
|
|
#self.__core.disable_chat(linphone.Reason.None) |
|
|
|
self.__core.echo_cancellation_enabled = False |
|
|
|
#self.__core.echo_cancellation_enabled = False |
|
|
|
self.__core.video_capture_enabled = False |
|
|
|
#self.__core.video_capture_enabled = False |
|
|
|
self.__core.video_display_enabled = False |
|
|
|
#self.__core.video_display_enabled = False |
|
|
|
|
|
|
|
|
|
|
|
def __global_state_changed(self, core, state, msg): |
|
|
|
|
|
|
|
print 'Global state changed:', state, msg |
|
|
|
def run_callbacks(self, evt): |
|
|
|
# TODO: Do we need events emitted here? |
|
|
|
print(PhoneEvent.string(evt)) |
|
|
|
pass |
|
|
|
for cb in self.__event_cbs: |
|
|
|
|
|
|
|
cb(evt) |
|
|
|
def __registration_state_changed(self, core, proxyconf, state, msg): |
|
|
|
|
|
|
|
print 'Registration state changed:', proxyconf, state, msg |
|
|
|
def on_LinphoneCallIncomingReceived(self, event): |
|
|
|
evt = None |
|
|
|
self.run_callbacks(PhoneEvent.CallIncoming) |
|
|
|
if state == linphone.RegistrationState.Progress: |
|
|
|
|
|
|
|
evt = PhoneEvent.RegInProgress |
|
|
|
def on_LinphoneCallOutgoingRinging(self, event): |
|
|
|
elif state == linphone.RegistrationState.Ok: |
|
|
|
self.run_callbacks(PhoneEvent.CallRinging) |
|
|
|
evt = PhoneEvent.RegSuccessfull |
|
|
|
|
|
|
|
elif state == linphone.RegistrationState.None: |
|
|
|
def on_LinphoneCallConnected(self, event): |
|
|
|
evt = PhoneEvent.RegLost |
|
|
|
self.run_callbacks(PhoneEvent.CallAccepted) |
|
|
|
|
|
|
|
|
|
|
|
if evt is not None: |
|
|
|
def on_LinphoneCallEnd(self, event): |
|
|
|
for cb in self.__event_cbs: |
|
|
|
self.run_callbacks(PhoneEvent.CallEnded) |
|
|
|
cb(evt) |
|
|
|
|
|
|
|
else: |
|
|
|
def on_LinphoneCallError(self, event): |
|
|
|
print 'Unhandled registration state:', linphone.RegistrationState.string(state) |
|
|
|
# TODO: Distinguish between different errors |
|
|
|
|
|
|
|
self.run_callbacks(PhoneEvent.CallBusy) |
|
|
|
def __call_state_changed(self, core, call, state, msg): |
|
|
|
|
|
|
|
print 'Call state changed:', call, state, msg |
|
|
|
|
|
|
|
evt = None |
|
|
|
|
|
|
|
if state == linphone.CallState.IncomingReceived: |
|
|
|
|
|
|
|
evt = PhoneEvent.CallIncoming |
|
|
|
|
|
|
|
elif state == linphone.CallState.OutgoingRinging: |
|
|
|
|
|
|
|
evt = PhoneEvent.CallRinging |
|
|
|
|
|
|
|
elif state == linphone.CallState.Connected: |
|
|
|
|
|
|
|
evt = PhoneEvent.CallAccepted |
|
|
|
|
|
|
|
elif state == linphone.CallState.End: |
|
|
|
|
|
|
|
evt = PhoneEvent.CallEnded |
|
|
|
|
|
|
|
elif state == linphone.CallState.Error: |
|
|
|
|
|
|
|
error = call.error_info.reason |
|
|
|
|
|
|
|
if error == linphone.Reason.Busy: |
|
|
|
|
|
|
|
evt = PhoneEvent.CallBusy |
|
|
|
|
|
|
|
elif error == linphone.Reason.NotFound: |
|
|
|
|
|
|
|
evt = PhoneEvent.CallInvalidNumber |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
evt = PhoneEvent.CallEnded |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if evt is not None: |
|
|
|
|
|
|
|
for cb in self.__event_cbs: |
|
|
|
|
|
|
|
cb(evt) |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
print 'Unhandled call state:', linphone.CallState.string(state) |
|
|
|
|
|
|
|
|
|
|
|
def __pollthread(self): |
|
|
|
def __pollthread(self): |
|
|
|
while self.__running: |
|
|
|
while self.__running: |
|
|
|
self.__core.process_event() |
|
|
|
self.__core.iterate() |
|
|
|
time.sleep(0.2) # Value for good measure |
|
|
|
time.sleep(0.02) # Value from example code |
|
|
|
|
|
|
|
|
|
|
|
def start(self): |
|
|
|
def start(self): |
|
|
|
self.__running = True |
|
|
|
self.__running = True |
|
|
@ -124,32 +167,23 @@ class PhoneInterface(object): |
|
|
|
self.__event_cbs.append(cb) |
|
|
|
self.__event_cbs.append(cb) |
|
|
|
|
|
|
|
|
|
|
|
def call(self, number): |
|
|
|
def call(self, number): |
|
|
|
if '@' not in number: |
|
|
|
if '@' not in number and self.__core.default_proxy_config is None: |
|
|
|
proxy = None |
|
|
|
# Try to resolve prefix |
|
|
|
default_name = self.__config.default_proxy |
|
|
|
|
|
|
|
for p in self.__config.proxies: |
|
|
|
for p in self.__config.proxies: |
|
|
|
if p.name == default_name: |
|
|
|
if number.startswith(p.prefix): |
|
|
|
proxy = p |
|
|
|
number = number[len(p.prefix):] |
|
|
|
|
|
|
|
number += '@' + p.realm |
|
|
|
break |
|
|
|
break |
|
|
|
if proxy is None: |
|
|
|
self.__core.invite(number) |
|
|
|
# Try to resolve prefix |
|
|
|
|
|
|
|
for p in self.__config.proxies: |
|
|
|
|
|
|
|
if number.startswith(p.prefix): |
|
|
|
|
|
|
|
number = number[len(p.prefix):] |
|
|
|
|
|
|
|
proxy = p |
|
|
|
|
|
|
|
break |
|
|
|
|
|
|
|
if proxy is not None: |
|
|
|
|
|
|
|
number += '@' + proxy.realm |
|
|
|
|
|
|
|
self.__core.call(number) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def accept_call(self): |
|
|
|
def accept_call(self): |
|
|
|
self.__core.answer() |
|
|
|
self.__core.accept_call(self.__core.current_call) |
|
|
|
|
|
|
|
|
|
|
|
def decline_call(self): |
|
|
|
def decline_call(self): |
|
|
|
self.__core.decline_call(self.__core.current_call) |
|
|
|
self.__core.decline_call(self.__core.current_call, linphone.Reason.Busy) |
|
|
|
|
|
|
|
|
|
|
|
def end_call(self): |
|
|
|
def end_call(self): |
|
|
|
self.__core.terminate() |
|
|
|
self.__core.terminate_call(self.__core.current_call) |
|
|
|
|
|
|
|
|
|
|
|
def play_dial_tone(self): |
|
|
|
def play_dial_tone(self): |
|
|
|
self.stop_playing() |
|
|
|
self.stop_playing() |
|
|
@ -179,17 +213,15 @@ class PhoneInterface(object): |
|
|
|
self.__audioproc.terminate() |
|
|
|
self.__audioproc.terminate() |
|
|
|
|
|
|
|
|
|
|
|
def read_text(self, text): |
|
|
|
def read_text(self, text): |
|
|
|
self.__ttsproc.stdin.write(text.encode('utf8') + b'\n') |
|
|
|
self.__ttsproc.stdin.write(text + '\n') |
|
|
|
self.__ttsproc.stdin.flush() |
|
|
|
self.__ttsproc.stdin.flush() |
|
|
|
|
|
|
|
|
|
|
|
def get_remote_number(self): |
|
|
|
def get_remote_number(self): |
|
|
|
# FIXME |
|
|
|
return self.__core.current_call_remote_address.username |
|
|
|
#return self.__core.current_call_remote_address.username |
|
|
|
|
|
|
|
return '0000' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__': |
|
|
|
if __name__ == '__main__': |
|
|
|
def event_cb(evt): |
|
|
|
def event_cb(evt): |
|
|
|
print('Got event:', PhoneEvent.string(evt)) |
|
|
|
print 'Got event:', PhoneEvent.string(evt) |
|
|
|
|
|
|
|
|
|
|
|
try: |
|
|
|
try: |
|
|
|
phone = PhoneInterface('.linphonerc-foo', '.linphonerc') |
|
|
|
phone = PhoneInterface('.linphonerc-foo', '.linphonerc') |
|
|
|