blob: 1ff98a91e756fc0bf9d6c5fbdd52dbab0d5034bd [file] [log] [blame]
Neels Hofmeyr3531a192017-03-28 14:30:28 +02001# osmo_gsm_tester: DBUS client to talk to ofono
2#
3# Copyright (C) 2016-2017 by sysmocom - s.f.m.c. GmbH
4#
5# Author: Neels Hofmeyr <neels@hofmeyr.de>
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
Pau Espin Pedrol927344b2017-05-22 16:38:49 +020020from . import log, test, util, event_loop
Neels Hofmeyr3531a192017-03-28 14:30:28 +020021
22from pydbus import SystemBus, Variant
23import time
24import pprint
25
26from gi.repository import GLib
27glib_main_loop = GLib.MainLoop()
28glib_main_ctx = glib_main_loop.get_context()
29bus = SystemBus()
30
Pau Espin Pedrol504a6642017-05-04 11:38:23 +020031I_MODEM = 'org.ofono.Modem'
Neels Hofmeyrb3daaea2017-04-09 14:18:34 +020032I_NETREG = 'org.ofono.NetworkRegistration'
33I_SMS = 'org.ofono.MessageManager'
34
Neels Hofmeyr035cda82017-05-05 17:52:45 +020035class DeferredHandling:
36 defer_queue = []
37
38 def __init__(self, dbus_iface, handler):
39 self.handler = handler
Neels Hofmeyr47de6b02017-05-10 13:24:05 +020040 self.subscription_id = dbus_iface.connect(self.receive_signal)
Neels Hofmeyr035cda82017-05-05 17:52:45 +020041
42 def receive_signal(self, *args, **kwargs):
43 DeferredHandling.defer_queue.append((self.handler, args, kwargs))
44
45 @staticmethod
46 def handle_queue():
47 while DeferredHandling.defer_queue:
48 handler, args, kwargs = DeferredHandling.defer_queue.pop(0)
49 handler(*args, **kwargs)
50
51def dbus_connect(dbus_iface, handler):
52 '''This function shall be used instead of directly connecting DBus signals.
53 It ensures that we don't nest a glib main loop within another, and also
54 that we receive exceptions raised within the signal handlers. This makes it
55 so that a signal handler is invoked only after the DBus polling is through
56 by enlisting signals that should be handled in the
57 DeferredHandling.defer_queue.'''
Neels Hofmeyr47de6b02017-05-10 13:24:05 +020058 return DeferredHandling(dbus_iface, handler).subscription_id
Neels Hofmeyr035cda82017-05-05 17:52:45 +020059
Pau Espin Pedrol927344b2017-05-22 16:38:49 +020060def poll_glib():
Neels Hofmeyr3531a192017-03-28 14:30:28 +020061 global glib_main_ctx
62 while glib_main_ctx.pending():
63 glib_main_ctx.iteration()
Neels Hofmeyr035cda82017-05-05 17:52:45 +020064 DeferredHandling.handle_queue()
Neels Hofmeyr3531a192017-03-28 14:30:28 +020065
Pau Espin Pedrol927344b2017-05-22 16:38:49 +020066event_loop.register_poll_func(poll_glib)
67
Neels Hofmeyr93f58662017-05-03 16:32:16 +020068def systembus_get(path):
Neels Hofmeyr3531a192017-03-28 14:30:28 +020069 global bus
70 return bus.get('org.ofono', path)
71
72def list_modems():
Neels Hofmeyr93f58662017-05-03 16:32:16 +020073 root = systembus_get('/')
Neels Hofmeyr3531a192017-03-28 14:30:28 +020074 return sorted(root.GetModems())
75
76
77class Modem(log.Origin):
78 'convenience for ofono Modem interaction'
79 msisdn = None
Neels Hofmeyrfec7d162017-05-02 16:29:09 +020080 sms_received_list = None
Neels Hofmeyr3531a192017-03-28 14:30:28 +020081
82 def __init__(self, conf):
83 self.conf = conf
84 self.path = conf.get('path')
85 self.set_name(self.path)
86 self.set_log_category(log.C_BUS)
87 self._dbus_obj = None
Neels Hofmeyrb3daaea2017-04-09 14:18:34 +020088 self._interfaces = set()
Neels Hofmeyr47de6b02017-05-10 13:24:05 +020089 self._connected_signals = util.listdict()
Neels Hofmeyrfec7d162017-05-02 16:29:09 +020090 self.sms_received_list = []
Pau Espin Pedrol107f2752017-05-04 11:37:16 +020091 # init interfaces and connect to signals:
92 self.dbus_obj()
Pau Espin Pedrol927344b2017-05-22 16:38:49 +020093 event_loop.poll()
Neels Hofmeyr3531a192017-03-28 14:30:28 +020094
95 def set_msisdn(self, msisdn):
96 self.msisdn = msisdn
97
98 def imsi(self):
Neels Hofmeyrb02c2112017-04-09 18:46:48 +020099 imsi = self.conf.get('imsi')
100 if not imsi:
101 with self:
102 raise RuntimeError('No IMSI')
103 return imsi
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200104
105 def ki(self):
106 return self.conf.get('ki')
107
Pau Espin Pedrol504a6642017-05-04 11:38:23 +0200108 def _dbus_set_bool(self, name, bool_val, iface=I_MODEM):
Neels Hofmeyr9dbcb822017-05-02 14:57:57 +0200109 # to make sure any pending signals are received before we send out more DBus requests
Pau Espin Pedrol927344b2017-05-22 16:38:49 +0200110 event_loop.poll()
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200111
Neels Hofmeyr9dbcb822017-05-02 14:57:57 +0200112 val = bool(bool_val)
113 self.log('Setting', name, val)
Pau Espin Pedrol504a6642017-05-04 11:38:23 +0200114 self.dbus_obj()[iface].SetProperty(name, Variant('b', val))
Neels Hofmeyr9dbcb822017-05-02 14:57:57 +0200115
Pau Espin Pedrol927344b2017-05-22 16:38:49 +0200116 event_loop.wait(self, self.property_is, name, bool_val)
Neels Hofmeyr27d459c2017-05-02 16:30:18 +0200117
118 def property_is(self, name, val):
119 is_val = self.properties().get(name)
120 self.dbg(name, '==', is_val)
121 return is_val is not None and is_val == val
Neels Hofmeyr9dbcb822017-05-02 14:57:57 +0200122
123 def set_powered(self, on=True):
124 self._dbus_set_bool('Powered', on)
125
Pau Espin Pedrolb9955762017-05-02 09:39:27 +0200126 def set_online(self, on=True):
Neels Hofmeyr9dbcb822017-05-02 14:57:57 +0200127 self._dbus_set_bool('Online', on)
Pau Espin Pedrolb9955762017-05-02 09:39:27 +0200128
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200129 def dbus_obj(self):
130 if self._dbus_obj is not None:
131 return self._dbus_obj
Neels Hofmeyr93f58662017-05-03 16:32:16 +0200132 self._dbus_obj = systembus_get(self.path)
Neels Hofmeyr035cda82017-05-05 17:52:45 +0200133 dbus_connect(self._dbus_obj.PropertyChanged, self._on_property_change)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200134 self._on_interfaces_change(self.properties().get('Interfaces'))
Neels Hofmeyrb02c2112017-04-09 18:46:48 +0200135 return self._dbus_obj
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200136
Pau Espin Pedrol504a6642017-05-04 11:38:23 +0200137 def properties(self, iface=I_MODEM):
138 return self.dbus_obj()[iface].GetProperties()
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200139
140 def _on_property_change(self, name, value):
141 if name == 'Interfaces':
142 self._on_interfaces_change(value)
143
144 def _on_interfaces_change(self, interfaces_now):
145 now = set(interfaces_now)
Neels Hofmeyrb3daaea2017-04-09 14:18:34 +0200146 additions = now - self._interfaces
147 removals = self._interfaces - now
148 self._interfaces = now
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200149 for iface in removals:
Neels Hofmeyr5a602b72017-05-06 22:45:15 +0200150 self._on_interface_disabled(iface)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200151 for iface in additions:
Neels Hofmeyr5a602b72017-05-06 22:45:15 +0200152 self._on_interface_enabled(iface)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200153
Neels Hofmeyr47de6b02017-05-10 13:24:05 +0200154 def _disconnect(self, interface_name):
155 subscriptions = self._connected_signals.pop(interface_name, [])
156 if subscriptions:
157 self.dbg('Disconnecting', len(subscriptions), 'signals from', interface_name)
158 for subscription in subscriptions:
159 subscription.disconnect()
160
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200161 def _on_interface_enabled(self, interface_name):
162 self.dbg('Interface enabled:', interface_name)
Neels Hofmeyr47de6b02017-05-10 13:24:05 +0200163
164 all_wanted_conns = {
165 I_SMS: ( ('IncomingMessage', self._on_incoming_message), ),
166 }
167
168 want = all_wanted_conns.get(interface_name)
169 if not want:
170 return
171
172 # sanity
173 self._disconnect(interface_name)
174
175 self.dbg('Connecting', len(want), 'signals to', interface_name)
176 for signal, cb in want:
177 dbus_iface = getattr(self.dbus_obj()[interface_name], signal)
178 self._connected_signals.add(interface_name, dbus_connect(dbus_iface, cb))
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200179
180 def _on_interface_disabled(self, interface_name):
181 self.dbg('Interface disabled:', interface_name)
Neels Hofmeyr47de6b02017-05-10 13:24:05 +0200182 self._disconnect(interface_name)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200183
Neels Hofmeyrb3daaea2017-04-09 14:18:34 +0200184 def has_interface(self, name):
185 return name in self._interfaces
186
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200187 def connect(self, nitb):
188 'set the modem up to connect to MCC+MNC from NITB config'
189 self.log('connect to', nitb)
Pau Espin Pedrol107f2752017-05-04 11:37:16 +0200190 prepowered = self.properties().get('Powered')
191 if prepowered is not None and prepowered:
192 self.set_online(False)
193 self.set_powered(False)
Neels Hofmeyrb3daaea2017-04-09 14:18:34 +0200194 self.set_powered()
Pau Espin Pedrolb9955762017-05-02 09:39:27 +0200195 self.set_online()
Neels Hofmeyrb3daaea2017-04-09 14:18:34 +0200196 if not self.has_interface(I_NETREG):
197 self.log('No %r interface, hoping that the modem connects by itself' % I_NETREG)
198 else:
199 self.log('Use of %r interface not implemented yet, hoping that the modem connects by itself' % I_NETREG)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200200
Neels Hofmeyr8d8b03e2017-05-06 18:19:46 +0200201 def sms_send(self, to_msisdn, *tokens):
Neels Hofmeyrb3daaea2017-04-09 14:18:34 +0200202 if hasattr(to_msisdn, 'msisdn'):
203 to_msisdn = to_msisdn.msisdn
Neels Hofmeyr8d8b03e2017-05-06 18:19:46 +0200204 sms = Sms(self.msisdn, to_msisdn, 'from ' + self.name(), *tokens)
Neels Hofmeyr1ea59ea2017-05-02 14:41:54 +0200205 self.log('sending sms to MSISDN', to_msisdn, sms=sms)
Neels Hofmeyrb3daaea2017-04-09 14:18:34 +0200206 if not self.has_interface(I_SMS):
207 raise RuntimeError('Modem cannot send SMS, interface not active: %r' % I_SMS)
Neels Hofmeyrb02c2112017-04-09 18:46:48 +0200208 mm = self.dbus_obj()[I_SMS]
Neels Hofmeyrb3daaea2017-04-09 14:18:34 +0200209 mm.SendMessage(to_msisdn, str(sms))
210 return sms
211
212 def _on_incoming_message(self, message, info):
Neels Hofmeyr2e41def2017-05-06 22:42:57 +0200213 self.log('Incoming SMS:', repr(message))
Neels Hofmeyrf49c7da2017-05-06 22:43:32 +0200214 self.dbg(info=info)
Neels Hofmeyrfec7d162017-05-02 16:29:09 +0200215 self.sms_received_list.append((message, info))
Neels Hofmeyrb3daaea2017-04-09 14:18:34 +0200216
Neels Hofmeyr863cb562017-05-02 16:27:59 +0200217 def sms_was_received(self, sms):
Neels Hofmeyrfec7d162017-05-02 16:29:09 +0200218 for msg, info in self.sms_received_list:
219 if sms.matches(msg):
Neels Hofmeyr2e41def2017-05-06 22:42:57 +0200220 self.log('SMS received as expected:', repr(msg))
Neels Hofmeyrf49c7da2017-05-06 22:43:32 +0200221 self.dbg(info=info)
Neels Hofmeyrfec7d162017-05-02 16:29:09 +0200222 return True
223 return False
Neels Hofmeyrb3daaea2017-04-09 14:18:34 +0200224
225class Sms:
226 _last_sms_idx = 0
227 msg = None
228
Neels Hofmeyr8d8b03e2017-05-06 18:19:46 +0200229 def __init__(self, from_msisdn=None, to_msisdn=None, *tokens):
Neels Hofmeyrb3daaea2017-04-09 14:18:34 +0200230 Sms._last_sms_idx += 1
231 msgs = ['message nr. %d' % Sms._last_sms_idx]
Neels Hofmeyr8d8b03e2017-05-06 18:19:46 +0200232 msgs.extend(tokens)
Neels Hofmeyrb3daaea2017-04-09 14:18:34 +0200233 if from_msisdn:
Neels Hofmeyr8d8b03e2017-05-06 18:19:46 +0200234 msgs.append('from %s' % from_msisdn)
Neels Hofmeyrb3daaea2017-04-09 14:18:34 +0200235 if to_msisdn:
Neels Hofmeyr8d8b03e2017-05-06 18:19:46 +0200236 msgs.append('to %s' % to_msisdn)
237 self.msg = ', '.join(msgs)
Neels Hofmeyrb3daaea2017-04-09 14:18:34 +0200238
239 def __str__(self):
240 return self.msg
241
Neels Hofmeyrfec7d162017-05-02 16:29:09 +0200242 def __repr__(self):
243 return repr(self.msg)
244
Neels Hofmeyrb3daaea2017-04-09 14:18:34 +0200245 def __eq__(self, other):
246 if isinstance(other, Sms):
247 return self.msg == other.msg
248 return inself.msg == other
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200249
Neels Hofmeyrfec7d162017-05-02 16:29:09 +0200250 def matches(self, msg):
251 return self.msg == msg
252
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200253# vim: expandtab tabstop=4 shiftwidth=4