Completed
Push — master ( 0bc61b...16fc24 )
by Thomas
8s
created

doorpi/sipphone/from_linphone.py (6 issues)

1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
4
import logging
5
logger = logging.getLogger(__name__)
6
logger.debug("%s loaded", __name__)
7
8
import datetime
9
10
from AbstractBaseClass import SipphoneAbstractBaseClass, SIPPHONE_SECTION
11
import linphone as lin
12
13
from doorpi import DoorPi
14
from doorpi.sipphone.linphone_lib.CallBacks import LinphoneCallbacks
15
from doorpi.sipphone.linphone_lib.Player import LinphonePlayer
16
from doorpi.sipphone.linphone_lib.Recorder import LinphoneRecorder
17
from doorpi.media.CreateDialTone import generate_dial_tone
0 ignored issues
show
Unused generate_dial_tone imported from doorpi.media.CreateDialTone
Loading history...
18
19
conf = DoorPi().config
20
21
def log_handler(level, msg):
22
    if "pylinphone_Core_instance_method_iterate" in msg: return
23
    if "pylinphone_Core_get_current_call" in msg: return
24
    if "pylinphone_Call_from_native_ptr" in msg: return
25
    if ": keep alive sent to [" in msg: return
26
    method = getattr(logger, level)
27
    method(msg)
28
29
if logger.getEffectiveLevel() <= 5: lin.set_log_handler(log_handler)
30
31
def get(*args, **kwargs): return LinPhone(*args, **kwargs)
32
class LinPhone(SipphoneAbstractBaseClass):
33
34
    @property
35
    def name(self): return 'linphone wrapper'
36
37
    @property
38
    def lib(self): return self.__Lib
39
    @property
40
    def core(self): return self.__Lib
41
42
    @property
43
    def recorder(self): return self.__recorder
44
    __recorder = None
45
46
    @property
47
    def player(self): return self.__player
48
    __player = None
49
50
    @property
51
    def current_call(self): return self.core.current_call
52
53
    @property
54
    def video_devices(self):
55
        try:
56
            all_devices = []
57
            for video_device in self.core.video_devices:
58
                all_devices.append({
59
                  'name':       video_device
60
                })
61
            return all_devices
62
        except Exception:
63
            return []
64
65
    @property
66
    def sound_devices(self):
67
        try:
68
            all_devices = []
69
            for sound_device in self.core.sound_devices:
70
                all_devices.append({
71
                  'name':       sound_device,
72
                  'capture':    self.core.sound_device_can_capture(sound_device),
73
                  'record':     self.core.sound_device_can_playback(sound_device)
74
                })
75
            return all_devices
76
        except Exception as exp:
77
            logger.exception(exp)
78
            return []
79
80
    def _create_payload_enum(self, payloads):
81
82
        try:
83
            all_codecs = []
84
            for codec in payloads:
85
                all_codecs.append({
86
                    'name':         codec.mime_type,
87
                    'channels':     codec.channels,
88
                    'bitrate':      codec.normal_bitrate,
89
                    'enable':       self.core.payload_type_enabled(codec)
90
                })
91
            return all_codecs
92
        except Exception as exp:
93
            logger.exception(exp)
94
            return []
95
96
    @property
97
    def video_codecs(self):
98
        return self._create_payload_enum(self.core.video_codecs)
99
100
    @property
101
    def sound_codecs(self):
102
        return self._create_payload_enum(self.core.audio_codecs)
103
104
    @property
105
    def current_call_duration(self):
106
        if not self.current_call: return 0
107
        diff_start_and_now = datetime.datetime.utcnow() - self.__current_call_start_datetime
108
        return diff_start_and_now.total_seconds()
109
110
    @property
111
    def current_call_dump(self):
112
        try:
113
            return {
114
                'direction':        'incoming' if self.current_call.dir == 0 else 'outgoing',
115
                'remote_uri':       self.current_call.remote_address_as_string,
116
                'total_time':       self.current_call_duration,
117
                'level_incoming':   self.current_call.record_volume,
118
                'level_outgoing':   self.current_call.play_volume,
119
                'camera':           self.current_call.camera_enabled
120
            }
121
        except Exception:
122
            return {}
123
124
    #TODO: Datetime from linphone CallLog.start_date is more then 30 sec different to python datetime.utcnow()?
125
    __current_call_start_datetime = datetime.datetime.utcnow()
126
127
    @property
128
    def base_config(self):
129
        params = self.core.create_call_params(None)
130
        params.record_file = self.recorder.parsed_record_filename
131
        params.video_enabled = True
132
        return params
133
134
    def reset_call_start_datetime(self):
135
        self.__current_call_start_datetime = datetime.datetime.utcnow()
136
        logger.debug('reset current call start datetime to %s', self.__current_call_start_datetime)
137
        return self.__current_call_start_datetime
138
139
    def __init__(self, whitelist = list(), *args, **kwargs):
0 ignored issues
show
The argument kwargs seems to be unused.
Loading history...
The argument whitelist seems to be unused.
Loading history...
The argument args seems to be unused.
Loading history...
140
        logger.debug("__init__")
141
142
        DoorPi().event_handler.register_action('OnShutdown', self.destroy)
143
144
        DoorPi().event_handler.register_event('OnSipPhoneCreate', __name__)
145
        DoorPi().event_handler.register_event('OnSipPhoneStart', __name__)
146
        DoorPi().event_handler.register_event('OnSipPhoneDestroy', __name__)
147
148
        DoorPi().event_handler.register_event('OnSipPhoneRecorderCreate', __name__)
149
        DoorPi().event_handler.register_event('OnSipPhoneRecorderDestroy', __name__)
150
151
        DoorPi().event_handler.register_event('BeforeSipPhoneMakeCall', __name__)
152
        DoorPi().event_handler.register_event('OnSipPhoneMakeCall', __name__)
153
        DoorPi().event_handler.register_event('OnSipPhoneMakeCallFailed', __name__)
154
        DoorPi().event_handler.register_event('AfterSipPhoneMakeCall', __name__)
155
        
156
        DoorPi().event_handler.register_event('OnSipPhoneCallTimeoutNoResponse', __name__)
157
        DoorPi().event_handler.register_event('OnSipPhoneCallTimeoutMaxCalltime', __name__)
158
159
        DoorPi().event_handler.register_event('OnPlayerCreated', __name__)
160
161
        #http://pythonhosted.org/linphone/api_reference.html#linphone.Core.new
162
        self.callback = LinphoneCallbacks()
163
        config_path = None
164
        factory_config_path = None
165
        self.__Lib = lin.Core.new(
166
            self.callback.used_callbacks,
167
            config_path,
168
            factory_config_path
169
        )
170
        self.core.primary_contact = '%s <sip:[email protected]>'%conf.get(SIPPHONE_SECTION, "identity", 'DoorPi')
171
172
    def start(self):
173
        DoorPi().event_handler('OnSipPhoneCreate', __name__)
174
        self.core.max_calls = conf.get_int(SIPPHONE_SECTION, 'ua.max_calls', 2)
175
        self.core.echo_cancellation_enabled = conf.get_bool(SIPPHONE_SECTION, 'echo_cancellation_enabled', False)
176
        
177
        # set local listen ports, default: random
178
        self.core.sip_transports = lin.SipTransports(conf.get_int(SIPPHONE_SECTION, 'local_port', 5060), conf.get_int(SIPPHONE_SECTION, 'local_port', 5060), -1, -1)
179
180
        self.core.video_display_enabled = conf.get_bool(SIPPHONE_SECTION, 'video_display_enabled', False)
181
        self.core.stun_server = conf.get(SIPPHONE_SECTION, 'stun_server', '')
182
        firewall_policy = conf.get(SIPPHONE_SECTION, 'FirewallPolicy', 'PolicyNoFirewall')
183
        if firewall_policy == "PolicyNoFirewall": self.core.firewall_policy = lin.FirewallPolicy.PolicyNoFirewall
184
        elif firewall_policy == "PolicyUseNatAddress": self.core.firewall_policy = lin.FirewallPolicy.PolicyUseNatAddress
185
        elif firewall_policy == "PolicyUseStun": self.core.firewall_policy = lin.FirewallPolicy.PolicyUseStun
186
        elif firewall_policy == "PolicyUseIce": self.core.firewall_policy = lin.FirewallPolicy.PolicyUseIce
187
        elif firewall_policy == "PolicyUseUpnp": self.core.firewall_policy = lin.FirewallPolicy.PolicyUseUpnp
188
        else: self.core.firewall_policy = lin.FirewallPolicy.PolicyNoFirewall
189
190
        #http://pythonhosted.org/linphone/api_reference.html#linphone.Core.in_call_timeout
191
        #After this timeout period, the call is automatically hangup.
192
        self.core.in_call_timeout = conf.get_int(SIPPHONE_SECTION, 'max_call_time', 120)
193
        #http://pythonhosted.org/linphone/api_reference.html#linphone.Core.inc_timeout
194
        #If an incoming call isn’t answered for this timeout period, it is automatically declined.
195
        self.core.inc_timeout = conf.get_int(SIPPHONE_SECTION, 'call_timeout', 15)
196
197
        self.__player = LinphonePlayer()
198
        self.core.ringback = self.player.player_filename
199
        self.__recorder = LinphoneRecorder()
200
201
        if len(self.core.sound_devices) == 0:
202
            logger.warning('no audio devices available')
203
        else:
204
            self.core.capture_device = conf.get(SIPPHONE_SECTION, 'capture_device', self.core.capture_device)
205
            self.core.playback_device = conf.get(SIPPHONE_SECTION, 'playback_device', self.core.playback_device)
206
            logger.info("found %s possible sounddevices:", len(self.core.sound_devices))
207
            logger.debug("|rec|play| name")
208
            logger.debug("------------------------------------")
209
            for sound_device in self.core.sound_devices:
210
                logger.debug("| %s | %s  | %s",
211
                    'X' if self.core.sound_device_can_capture(sound_device) else 'O',
212
                    'X' if self.core.sound_device_can_playback(sound_device) else 'O',
213
                    sound_device
214
                )
215
            logger.debug("------------------------------------")
216
            logger.debug("using capture_device: %s", self.core.capture_device)
217
            logger.debug("using playback_device: %s", self.core.playback_device)
218
219
        # Only enable PCMU and PCMA audio codecs by default
220
        config_audio_codecs = conf.get_list(SIPPHONE_SECTION, 'audio_codecs', 'PCMA,PCMU')
221
        for codec in self.core.audio_codecs:
222
            if codec.mime_type in config_audio_codecs:
223
                logger.debug('enable audio codec %s', codec.mime_type)
224
                self.core.enable_payload_type(codec, True)
225
            else:
226
                logger.debug('disable audio codec %s', codec.mime_type)
227
                self.core.enable_payload_type(codec, False)
228
229
230
        if len(self.core.video_devices) == 0:
231
            self.core.video_capture_enabled = False
232
            logger.warning('no video devices available')
233
        else:
234
            logger.info("found %s possible videodevices:", len(self.core.video_devices))
235
            logger.debug("| name")
236
            logger.debug("------------------------------------")
237
            for video_device in self.core.video_devices:
238
                logger.debug("| %s ", video_device)
239
            logger.debug("------------------------------------")
240
            config_camera = conf.get(SIPPHONE_SECTION, 'video_device', self.core.video_devices[0])
241
            if config_camera not in self.core.video_devices:
242
                logger.warning('camera "%s" from config does not exist in possible video devices.', config_camera)
243
                logger.debug('switching to first possible video device "%s"', self.core.video_devices[0])
244
                config_camera = self.core.video_devices[0]
245
246
            self.core.video_capture_enabled = True
247
            self.core.video_device = config_camera
248
            self.core.preferred_video_size_by_name = conf.get(SIPPHONE_SECTION, 'video_size', 'vga')
249
            logger.debug("using video_device: %s", self.core.video_device)
250
251
        # Only enable VP8 video codec
252
        config_video_codecs = conf.get_list(SIPPHONE_SECTION, 'video_codecs', 'VP8')
253
        for codec in self.core.video_codecs:
254
            if codec.mime_type in config_video_codecs and self.core.video_capture_enabled:
255
                logger.debug('enable video codec %s', codec.mime_type)
256
                self.core.enable_payload_type(codec, True)
257
            else:
258
                logger.debug('disable video codec %s', codec.mime_type)
259
                self.core.enable_payload_type(codec, False)
260
261
        # Configure the SIP account
262
        server = conf.get(SIPPHONE_SECTION, "sipserver_server")
263
        username = conf.get(SIPPHONE_SECTION, "sipserver_username")
264
        password = conf.get(SIPPHONE_SECTION, "sipserver_password", username)
265
        realm = conf.get(SIPPHONE_SECTION, "sipserver_realm", server)
266
        if server and username and password:
267
            logger.info('using DoorPi with SIP-Server')
268
            proxy_cfg = self.core.create_proxy_config()
269
            proxy_cfg.identity_address = lin.Address.new("%s <sip:%[email protected]%s>" % (
270
                    conf.get(SIPPHONE_SECTION, "identity", 'DoorPi'), username, server)
271
            )
272
            proxy_cfg.server_addr = "sip:%s"%server
273
            proxy_cfg.register_enabled = True
274
            self.core.add_proxy_config(proxy_cfg)
275
            self.core.default_proxy_config = proxy_cfg
276
            auth_info = self.core.create_auth_info(username, None, password, None, None, realm)
277
            self.core.add_auth_info(auth_info)
278
        else:
279
            logger.info('using DoorPi without SIP-Server? Okay...')
280
            proxy_cfg = self.core.create_proxy_config()
281
            proxy_cfg.register_enabled = False
282
            self.core.add_proxy_config(proxy_cfg)
283
            self.core.default_proxy_config = proxy_cfg
284
            logger.debug('%s',self.core.proxy_config_list)
285
286
        logger.debug("start successfully")
287
288
    def destroy(self):
289
        logger.debug("destroy")
290
        self.core.terminate_all_calls()
291
        DoorPi().event_handler.fire_event_synchron('OnSipPhoneDestroy', __name__)
292
        DoorPi().event_handler.unregister_source(__name__, True)
293
        return
294
295
    def self_check(self, *args, **kwargs):
0 ignored issues
show
The argument kwargs seems to be unused.
Loading history...
The argument args seems to be unused.
Loading history...
296
        if not self.core: return
297
298
        self.core.iterate()
299
300
        if not self.current_call: return
301
302
        if self.current_call.state < lin.CallState.Connected:
303
            if self.current_call_duration > self.core.inc_timeout - 0.5:
304
                logger.info("call timeout - hangup current call after %s seconds (max. %s)", self.current_call_duration, self.core.inc_timeout)
305
                self.core.terminate_all_calls()
306
                DoorPi().event_handler('OnSipPhoneCallTimeoutNoResponse', __name__)
307
        else:
308
            if int(self.current_call_duration) > self.core.in_call_timeout - 0.5:
309
                logger.info("max call time reached - hangup current call after %s seconds (max. %s)", self.current_call_duration, self.core.in_call_timeout)
310
                self.core.terminate_all_calls()
311
                DoorPi().event_handler('OnSipPhoneCallTimeoutMaxCalltime', __name__)
312
313
    def call(self, number):
314
        DoorPi().event_handler('BeforeSipPhoneMakeCall', __name__, {'number':number})
315
        logger.debug("call (%s)",str(number))
316
        if not self.current_call:
317
            logger.debug('no current call -> start new call')
318
            self.reset_call_start_datetime()
319
            if self.core.invite_with_params(number, self.base_config) is None:
320
                if DoorPi().event_handler.db.get_event_log_entries_count('OnSipPhoneMakeCallFailed') > 5:
321
                    logger.error('failed to execute call five times')
322
                else:
323
                    DoorPi().event_handler('OnSipPhoneMakeCallFailed', __name__, {'number':number})
324
                return None
325
            DoorPi().event_handler('OnSipPhoneMakeCall', __name__, {'number':number})
326
        elif number in self.current_call.remote_address.as_string_uri_only():
327
            if self.current_call_duration <= 2:
328
                logger.debug("same call %s again while call is running since %s seconds? -> skip",
329
                             self.core.current_call.remote_address.as_string_uri_only(),
330
                             self.current_call_duration
331
                )
332
            else:
333
                logger.debug("press twice with call duration > 1 second? Want to hangup current call? OK...")
334
                self.core.terminate_all_calls()
335
        else:
336
            logger.debug("new call needed? hangup old first...")
337
            self.core.terminate_all_calls()
338
            self.call(number)
339
340
        DoorPi().event_handler('AfterSipPhoneMakeCall', __name__, {'number':number})
341
        return self.current_call
342
343
    def is_admin_number(self, remote_uri):
344
        return self.callback.is_admin_number(remote_uri)
345
346
    def hangup(self):
347
        if self.current_call:
348
            logger.debug("Received hangup request, cancelling current call")
349
            self.core.terminate_call(self.current_call)
350
        else:
351
            logger.debug("Ignoring hangup request as there is no ongoing call")
352
353