linphone-iphone/tools/python/linphone-daemon.py

514 lines
15 KiB
Python

import argparse
import logging
import sys
import threading
import time
from linphone import linphone
class Response:
Ok = 0
Error = 1
def __init__(self, status, msg = ''):
self.status = status
if status == Response.Ok:
self.body = msg
self.reason = None
else:
self.body = None
self.reason = msg
def __str__(self):
status_str = ["Ok", "Error"][self.status]
body = ''
if self.reason:
body += "Reason: {reason}\n".format(reason=self.reason)
if self.body:
body += '\n' + self.body + '\n'
return \
"""Status: {status}
{body}""".format(status=status_str, body=body)
class RegisterStatusResponse(Response):
def __init__(self):
Response.__init__(self, Response.Ok)
def append(self, id, proxy_cfg):
self.body += \
"""Id: {id}
State: {state}
""".format(id=id, state=str(linphone.RegistrationState.string(proxy_cfg.state)))
class CommandExample:
def __init__(self, command, output):
self.command = command
self.output = output
def __str__(self):
return \
"""> {command}
{output}""".format(command=self.command, output=self.output)
class Command:
def __init__(self, name, proto):
self.name = name
self.proto = proto
self.examples = []
def exec_command(self, app, args):
pass
def add_example(self, example):
self.examples.append(example)
def help(self):
body = \
"""{proto}
Description:
{description}
""".format(proto=self.proto, description=self.__doc__)
idx = 0
for example in self.examples:
idx += 1
body += \
"""
Example {idx}:
{example}
""".format(idx=idx, example=str(example))
return body
class CallCommand(Command):
"""Place a call."""
def __init__(self):
Command.__init__(self, "call", "call <sip-address>")
self.add_example(CommandExample(
"call daemon-test@sip.linphone.org",
"Status: Ok\n\nId: 1"
))
self.add_example(CommandExample(
"call daemon-test@sip.linphone.org",
"Status: Error\nReason: Call creation failed."
))
def exec_command(self, app, args):
if len(args) >= 1:
call = app.core.invite(args[0])
if call is None:
app.send_response(Response(Response.Error, "Call creation failed."))
else:
id = app.update_call_id(call)
app.send_response(Response(Response.Ok, "Id: " + str(id)))
else:
app.send_response(Response(Response.Error, "Missing parameter."))
class CallPauseCommand(Command):
"""Pause a call (pause current if no id is specified)."""
def __init__(self):
Command.__init__(self, "call-pause", "call-pause [call-id]")
self.add_example(CommandExample(
"call-pause 1",
"Status: Ok\n\nCall was paused"
))
self.add_example(CommandExample(
"call-pause 2",
"Status: Error\nReason: No call with such id."
))
self.add_example(CommandExample(
"call-pause",
"Status: Error\nReason: No current call available."
))
def exec_command(self, app, args):
current = False
if len(args) >= 1:
call = app.find_call(args[0])
if call is None:
app.send_response(Response(Response.Error, "No call with such id."))
return
else:
current = True
call = app.core.current_call
if call is None:
app.send_response(Response(Response.Error, "No current call available."))
return
if app.core.pause_call(call) == 0:
msg = "Call was paused."
if current:
msg = "Current call was paused."
app.send_response(Response(Response.Ok, msg))
else:
app.send_response(Response(Response.Error, "Error pausing call."))
class CallResumeCommand(Command):
"""Resume a call (resume current if no id is specified)."""
def __init__(self):
Command.__init__(self, "call-resume", "call-resume [call-id]")
self.add_example(CommandExample(
"call-resume 1",
"Status: Ok\n\nCall was resumed"
))
self.add_example(CommandExample(
"call-resume 2",
"Status: Error\nReason: No call with such id."
))
self.add_example(CommandExample(
"call-resume",
"Status: Error\nReason: No current call available."
))
def exec_command(self, app, args):
current = False
if len(args) >= 1:
call = app.find_call(args[0])
if call is None:
app.send_response(Response(Response.Error, "No call with such id."))
return
else:
current = True
call = app.core.current_call
if call is None:
app.send_response(Response(Response.Error, "No current call available."))
return
if app.core.resume_call(call) == 0:
msg = "Call was resumed."
if current:
msg = "Current call was resumed."
app.send_response(Response(Response.Ok, msg))
else:
app.send_response(Response(Response.Error, "Error resuming call."))
class CallStatusCommand(Command):
"""Return status of the specified call or of the current call if no id is given."""
def __init__(self):
Command.__init__(self, "call-status", "call-status [call-id]")
self.add_example(CommandExample(
"call-status 1",
"Status: Ok\n\nState: LinphoneCallStreamsRunning\nFrom: <sip:daemon-test@sip.linphone.org>\nDirection: out\nDuration: 6"
))
self.add_example(CommandExample(
"call-status 2",
"Status: Error\nReason: No call with such id."
))
self.add_example(CommandExample(
"call-status",
"Status: Error\nReason: No current call available."
))
def exec_command(self, app, args):
if len(args) >= 1:
call = app.find_call(args[0])
if call is None:
app.send_response(Response(Response.Error, "No call with such id."))
return
else:
call = app.core.current_call
if call is None:
app.send_response(Response(Response.Error, "No current call available."))
return
state = call.state
body = "State: {state}".format(state=linphone.CallState.string(state))
if state == linphone.CallState.CallOutgoingInit \
or state == linphone.CallState.CallOutgoingProgress \
or state == linphone.CallState.CallOutgoingRinging \
or state == linphone.CallState.CallPaused \
or state == linphone.CallState.CallStreamsRunning \
or state == linphone.CallState.CallConnected \
or state == linphone.CallState.CallIncomingReceived:
body += "\nFrom: {address}".format(address=call.remote_address.as_string())
if state == linphone.CallState.CallStreamsRunning \
or state == linphone.CallState.CallConnected:
direction_str = 'in'
if call.dir == linphone.CallDir.CallOutgoing:
direction_str = 'out'
body += "\nDirection: {direction}\nDuration: {duration}".format(direction=direction_str, duration=call.duration)
app.send_response(Response(Response.Ok, body))
class HelpCommand(Command):
"""Show <command> help notice, if command is unspecified or inexistent show all commands."""
def __init__(self):
Command.__init__(self, "help", "help <command>")
def exec_command(self, app, args):
body = ''
if args:
command = [item for item in app.commands if item.name == args[0]]
if command:
body = command[0].help()
else:
app.send_response(Response(Response.Error, "Unknown command '{command}'.".format(command=args[0])))
return
else:
for command in app.commands:
body += command.proto + '\n'
app.send_response(Response(Response.Ok, body))
class QuitCommand(Command):
"""Quit the application."""
def __init__(self):
Command.__init__(self, "quit", "quit")
def exec_command(self, app, args):
app.quit()
app.send_response(Response(Response.Ok))
class RegisterCommand(Command):
"""Register the daemon to a SIP proxy. If one of the parameters <password>, <userid> and <realm> is not needed, send the string "NULL"."""
def __init__(self):
Command.__init__(self, "register", "register <identity> <proxy-address> [password] [userid] [realm] [contact-parameters]")
self.add_example(CommandExample(
"register sip:daemon-test@sip.linphone.org sip.linphone.org password bob linphone.org",
"Status: Ok\n\nId: 1"
))
def exec_command(self, app, args):
if len(args) >= 2:
password = None
userid = None
realm = None
contact_parameters = None
identity = args[0]
proxy = args[1]
if len(args) > 2 and args[2] != "NULL":
password = args[2]
if len(args) > 3 and args[3] != "NULL":
userid = args[3]
if len(args) > 4 and args[4] != "NULL":
realm = args[4]
if len(args) > 5 and args[5] != "NULL":
contact_parameters = args[5]
proxy_cfg = app.core.create_proxy_config()
if password is not None:
addr = linphone.Address.new(identity)
if addr is not None:
info = linphone.AuthInfo.new(addr.username, userid, password, None, realm, None)
app.core.add_auth_info(info)
print(info)
proxy_cfg.identity = identity
proxy_cfg.server_addr = proxy
proxy_cfg.register_enabled = True
proxy_cfg.contact_parameters = contact_parameters
app.core.add_proxy_config(proxy_cfg)
id = app.update_proxy_id(proxy_cfg)
app.send_response(Response(Response.Ok, "Id: " + str(id)))
else:
app.send_response(Response(Response.Error, "Missing/Incorrect parameter(s)."))
class RegisterStatusCommand(Command):
"""Return status of a registration or of all registrations."""
def __init__(self):
Command.__init__(self, "register-status", "register-status <register_id|ALL>")
self.add_example(CommandExample(
"register-status 1",
"Status: Ok\n\nId: 1\nState: LinphoneRegistrationOk"
))
self.add_example(CommandExample(
"register-status ALL",
"Status: Ok\n\nId: 1\nState: LinphoneRegistrationOk\n\nId: 2\nState: LinphoneRegistrationFailed"
))
self.add_example(CommandExample(
"register-status 3",
"Status: Error\nReason: No register with such id."
))
def exec_command(self, app, args):
if len(args) == 0:
app.send_response(Response(Response.Error, "Missing parameter."))
else:
id = args[0]
if id == "ALL":
response = RegisterStatusResponse()
for id in app.proxy_ids_map:
response.append(id, app.proxy_ids_map[id])
app.send_response(response)
else:
proxy_cfg = app.find_proxy(id)
if proxy_cfg is None:
app.send_response(Response(Response.Error, "No register with such id."))
else:
app.send_response(RegisterStatusResponse().append(id, proxy_cfg))
class TerminateCommand(Command):
"""Terminate the specified call or the current call if no id is given."""
def __init__(self):
Command.__init__(self, "terminate", "terminate [call id]")
self.add_example(CommandExample(
"terminate 2",
"Status: Error\nReason: No call with such id."
))
self.add_example(CommandExample(
"terminate 1",
"Status: Ok\n"
))
self.add_example(CommandExample(
"terminate",
"Status: Ok\n"
))
self.add_example(CommandExample(
"terminate",
"Status: Error\nReason: No active call."
))
def exec_command(self, app, args):
if len(args) >= 1:
call = app.find_call(args[0])
if call is None:
app.send_response(Response(Response.Error, "No call with such id."))
return
else:
call = app.core.current_call
if call is None:
app.send_response(Response(Response.Error, "No active call."))
return
app.core.terminate_call(call)
app.send_response(Response(Response.Ok))
class Daemon:
def __init__(self):
self.quitting = False
self._next_proxy_id = 1
self.proxy_ids_map = {}
self._next_call_id = 1
self.call_ids_map = {}
self.command_mutex = threading.Lock()
self.command_executed_event = threading.Event()
self.command_to_execute = None
self.commands = [
CallCommand(),
CallPauseCommand(),
CallResumeCommand(),
CallStatusCommand(),
HelpCommand(),
QuitCommand(),
RegisterCommand(),
RegisterStatusCommand(),
TerminateCommand()
]
def global_state_changed(self, core, state, message):
logging.warning("[PYTHON] global_state_changed: " + str(state) + ", " + message)
if state == linphone.GlobalState.GlobalOn:
logging.warning("[PYTHON] core version: " + str(core.version))
def registration_state_changed(self, core, proxy_cfg, state, message):
logging.warning("[PYTHON] registration_state_changed: " + str(state) + ", " + message)
def call_state_changed(self, core, call, state, message):
logging.warning("[PYTHON] call_state_changed: " + str(state) + ", " + message)
def send_response(self, response):
print(response)
def exec_command(self, command_line):
splitted_command_line = command_line.split()
name = splitted_command_line[0]
args = splitted_command_line[1:]
command = [item for item in self.commands if item.name == name]
if command:
command[0].exec_command(self, args)
else:
self.send_response(Response(Response.Error, "Unknown command."))
def interact(self):
command_line = raw_input('> ').strip()
if command_line != '':
self.command_mutex.acquire()
self.command_to_execute = command_line
self.command_mutex.release()
self.command_executed_event.wait()
self.command_executed_event.clear()
def run(self, args):
def command_read(daemon):
while not daemon.quitting:
daemon.interact()
callbacks = {
'global_state_changed':self.global_state_changed,
'registration_state_changed':self.registration_state_changed,
'call_state_changed':self.call_state_changed
}
# Create a linphone core and iterate every 20 ms
self.core = linphone.Core.new(callbacks, args.config, args.factory_config)
t = threading.Thread(target=command_read, kwargs={'daemon':self})
t.start()
while not self.quitting:
self.command_mutex.acquire()
command_line = self.command_to_execute
if command_line is not None:
self.exec_command(command_line)
self.command_to_execute = None
self.command_executed_event.set()
self.command_mutex.release()
self.core.iterate()
time.sleep(0.02)
t.join()
def quit(self):
self.quitting = True
def update_proxy_id(self, proxy):
id = self._next_proxy_id
self.proxy_ids_map[str(id)] = proxy
self._next_proxy_id += 1
return id
def find_proxy(self, id):
if self.proxy_ids_map.has_key(id):
return self.proxy_ids_map[id]
return None
def update_call_id(self, call):
id = self._next_call_id
self.call_ids_map[str(id)] = call
self._next_call_id += 1
return id
def find_call(self, id):
if self.call_ids_map.has_key(id):
return self.call_ids_map[id]
return None
def setup_log_colors():
logging.addLevelName(logging.DEBUG, "\033[1;37m%s\033[1;0m" % logging.getLevelName(logging.DEBUG))
logging.addLevelName(logging.INFO, "\033[1;36m%s\033[1;0m" % logging.getLevelName(logging.INFO))
logging.addLevelName(logging.WARNING, "\033[1;31m%s\033[1;0m" % logging.getLevelName(logging.WARNING))
logging.addLevelName(logging.ERROR, "\033[1;41m%s\033[1;0m" % logging.getLevelName(logging.ERROR))
def setup_log(log, trace):
if log is None:
setup_log_colors()
format = "%(asctime)s.%(msecs)03d %(levelname)s: %(message)s"
datefmt = "%H:%M:%S"
if trace:
level = logging.DEBUG
else:
level = logging.INFO
logging.basicConfig(filename=log, level=level, format=format, datefmt=datefmt)
# Define the linphone module log handler
def log_handler(level, msg):
method = getattr(logging, level)
if not msg.strip().startswith('[PYLINPHONE]'):
msg = '[CORE] ' + msg
method(msg)
def main(argv = None):
if argv is None:
argv = sys.argv
argparser = argparse.ArgumentParser(description="Linphone console interface in Python.")
argparser.add_argument('--config', default=None, help="Path to the linphonerc configuration file to use.")
argparser.add_argument('--factory_config', default=None, help="Path to the linphonerc factory configuration file to use.")
argparser.add_argument('--log', default=None, help="Path to the file used for logging (default is the standard output).")
argparser.add_argument('--trace', action='store_true', help="Output linphone Python module tracing logs (for debug purposes).")
args = argparser.parse_args()
setup_log(args.log, args.trace)
linphone.set_log_handler(log_handler)
d = Daemon()
d.run(args)
if __name__ == "__main__":
sys.exit(main())