Issues (158)

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.reset_last_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
            self.core.mic_gain_db = conf.get_float(SIPPHONE_SECTION, 'mic_gain_db', 0)
207
            logger.info("found %s possible sounddevices:", len(self.core.sound_devices))
208
            logger.debug("|rec|play| name")
209
            logger.debug("------------------------------------")
210
            for sound_device in self.core.sound_devices:
211
                logger.debug("| %s | %s  | %s",
212
                    'X' if self.core.sound_device_can_capture(sound_device) else 'O',
213
                    'X' if self.core.sound_device_can_playback(sound_device) else 'O',
214
                    sound_device
215
                )
216
            logger.debug("------------------------------------")
217
            logger.debug("using capture_device: %s", self.core.capture_device)
218
            logger.debug("using playback_device: %s", self.core.playback_device)
219
            logger.debug("mic_gain_db: %s", self.core.mic_gain_db)
220
221
        # Only enable PCMU and PCMA audio codecs by default
222
        config_audio_codecs = conf.get_list(SIPPHONE_SECTION, 'audio_codecs', 'PCMA,PCMU')
223
        for codec in self.core.audio_codecs:
224
            if codec.mime_type in config_audio_codecs:
225
                logger.debug('enable audio codec %s', codec.mime_type)
226
                self.core.enable_payload_type(codec, True)
227
            else:
228
                logger.debug('disable audio codec %s', codec.mime_type)
229
                self.core.enable_payload_type(codec, False)
230
231
232
        if len(self.core.video_devices) == 0:
233
            self.core.video_capture_enabled = False
234
            logger.warning('no video devices available')
235
        else:
236
            logger.info("found %s possible videodevices:", len(self.core.video_devices))
237
            logger.debug("| name")
238
            logger.debug("------------------------------------")
239
            for video_device in self.core.video_devices:
240
                logger.debug("| %s ", video_device)
241
            logger.debug("------------------------------------")
242
            config_camera = conf.get(SIPPHONE_SECTION, 'video_device', self.core.video_devices[0])
243
            if config_camera not in self.core.video_devices:
244
                logger.warning('camera "%s" from config does not exist in possible video devices.', config_camera)
245
                logger.debug('switching to first possible video device "%s"', self.core.video_devices[0])
246
                config_camera = self.core.video_devices[0]
247
248
            self.core.video_capture_enabled = True
249
            self.core.video_device = config_camera
250
            self.core.preferred_video_size_by_name = conf.get(SIPPHONE_SECTION, 'video_size', 'vga')
251
            logger.debug("using video_device: %s", self.core.video_device)
252
253
        # Only enable VP8 video codec
254
        config_video_codecs = conf.get_list(SIPPHONE_SECTION, 'video_codecs', 'VP8')
255
        for codec in self.core.video_codecs:
256
            if codec.mime_type in config_video_codecs and self.core.video_capture_enabled:
257
                logger.debug('enable video codec %s', codec.mime_type)
258
                self.core.enable_payload_type(codec, True)
259
            else:
260
                logger.debug('disable video codec %s', codec.mime_type)
261
                self.core.enable_payload_type(codec, False)
262
263
        # Configure the SIP account
264
        server = conf.get(SIPPHONE_SECTION, "sipserver_server")
265
        username = conf.get(SIPPHONE_SECTION, "sipserver_username")
266
        password = conf.get(SIPPHONE_SECTION, "sipserver_password", username)
267
        realm = conf.get(SIPPHONE_SECTION, "sipserver_realm", server)
268
        if server and username and password:
269
            logger.info('using DoorPi with SIP-Server')
270
            proxy_cfg = self.core.create_proxy_config()
271
            proxy_cfg.identity_address = lin.Address.new("%s <sip:%s@%s>" % (
272
                    conf.get(SIPPHONE_SECTION, "identity", 'DoorPi'), username, server)
273
            )
274
            proxy_cfg.server_addr = "sip:%s"%server
275
            proxy_cfg.register_enabled = True
276
            self.core.add_proxy_config(proxy_cfg)
277
            self.core.default_proxy_config = proxy_cfg
278
            auth_info = self.core.create_auth_info(username, None, password, None, None, realm)
279
            self.core.add_auth_info(auth_info)
280
        else:
281
            logger.info('using DoorPi without SIP-Server? Okay...')
282
            proxy_cfg = self.core.create_proxy_config()
283
            proxy_cfg.register_enabled = False
284
            self.core.add_proxy_config(proxy_cfg)
285
            self.core.default_proxy_config = proxy_cfg
286
            logger.debug('%s',self.core.proxy_config_list)
287
288
        logger.debug("start successfully")
289
290
    def destroy(self):
291
        logger.debug("destroy")
292
        self.core.terminate_all_calls()
293
        DoorPi().event_handler.fire_event_synchron('OnSipPhoneDestroy', __name__)
294
        DoorPi().event_handler.unregister_source(__name__, True)
295
        return
296
297
    def self_check(self, *args, **kwargs):
298
        if not self.core: return
299
300
        self.core.iterate()
301
302
        if not self.current_call: return
303
304
        if self.current_call.state < lin.CallState.Connected:
305
            if self.current_call_duration > self.core.inc_timeout - 0.5:
306
                logger.info("call timeout - hangup current call after %s seconds (max. %s)", self.current_call_duration, self.core.inc_timeout)
307
                self.core.terminate_all_calls()
308
                DoorPi().event_handler('OnSipPhoneCallTimeoutNoResponse', __name__)
309
        else:
310
            if int(self.current_call_duration) > self.core.in_call_timeout - 0.5:
311
                logger.info("max call time reached - hangup current call after %s seconds (max. %s)", self.current_call_duration, self.core.in_call_timeout)
312
                self.core.terminate_all_calls()
313
                DoorPi().event_handler('OnSipPhoneCallTimeoutMaxCalltime', __name__)
314
315
    def call(self, number):
316
        DoorPi().event_handler('BeforeSipPhoneMakeCall', __name__, {'number':number})
317
        logger.debug("call (%s)",str(number))
318
        if not self.current_call:
319
            logger.debug('no current call -> start new call')
320
            self.reset_call_start_datetime()
321
            if self.core.invite_with_params(number, self.base_config) is None:
322
                if DoorPi().event_handler.db.get_event_log_entries_count('OnSipPhoneMakeCallFailed') > 5:
323
                    logger.error('failed to execute call five times')
324
                else:
325
                    DoorPi().event_handler('OnSipPhoneMakeCallFailed', __name__, {'number':number})
326
                return None
327
            DoorPi().event_handler('OnSipPhoneMakeCall', __name__, {'number':number})
328
        elif number in self.current_call.remote_address.as_string_uri_only():
329
            if self.current_call_duration <= 2:
330
                logger.debug("same call %s again while call is running since %s seconds? -> skip",
331
                             self.core.current_call.remote_address.as_string_uri_only(),
332
                             self.current_call_duration
333
                )
334
            else:
335
                logger.debug("press twice with call duration > 1 second? Want to hangup current call? OK...")
336
                self.core.terminate_all_calls()
337
        else:
338
            logger.debug("new call needed? hangup old first...")
339
            self.core.terminate_all_calls()
340
            self.call(number)
341
342
        DoorPi().event_handler('AfterSipPhoneMakeCall', __name__, {'number':number})
343
        return self.current_call
344
345
    def is_admin_number(self, remote_uri):
346
        return self.callback.is_admin_number(remote_uri)
347
348
    def hangup(self):
349
        if self.current_call:
350
            logger.debug("Received hangup request, cancelling current call")
351
            self.core.terminate_call(self.current_call)
352
        else:
353
            logger.debug("Ignoring hangup request as there is no ongoing call")
354
355