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

doorpi/sipphone/from_linphone.py (1 issue)

Checks for unused imports

Unused Code Minor
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):
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):
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