Python libmm problems
Dan Williams
dcbw at redhat.com
Wed Sep 13 18:39:37 UTC 2017
On Wed, 2017-09-13 at 18:41 +0100, Colin Helliwell wrote:
> > On 13 September 2017 at 17:57 Dan Williams <dcbw at redhat.com> wrote:
> >
> > On Wed, 2017-09-13 at 17:23 +0100, colin.helliwell at ln-systems.com
> > wrote:
> >
> > > I'm trying to write some python to get the operator id off the
> > > modem.
> > > Due to limited knowledge of such things, I'm *not* using any
> > > notifcation
> > > callbacks etc, but simply trying to poll things.
> > > I've wrapped the functionality into a class (thinking to thus be
> > > able
> > > to
> > > easily contain the modem's objects etc in the instance).
> > > I poll an initial 'get modem' method:
> > >
> > > if self.connection is None:
> > > self.connection = Gio.bus_get_sync (Gio.BusType.SYSTEM,
> > > None)
> > > self.manager = ModemManager.Manager.new_sync
> > > (self.connection,
> > > Gio.DBusObjectManagerClientFlags.DO_NOT_AUTO_START, None)
> > > if self.manager is None:
> > >
> > > # MM not found
> > > self.connection = None
> > > self.modem = None
> > > self.obj = None
> > > return None
> > >
> > > # Search for the modem
> > > for obj in self.manager.get_objects():
> > > m = obj.get_modem()
> > >
> > > # Look for my modem
> > > if (m.get_primary_port() == "ttyS1"):
> > > self.obj = obj
> > > self.modem = m
> > > return self.modem
> > >
> > > Then, once the modem has been found by this, I go on to try to
> > > get
> > > the
> > > operator code by polling a further method:
> > >
> > > MccMnc = None
> > > if self.modem is not None:
> > >
> > > # Return MCC/MNC if registered
> > > if (self.modem.get_state() >=
> > > ModemManager.ModemState.REGISTERED):
> > > MccMnc =
> > > self.obj.get_modem_3gpp().get_operator_code()
> > > else:
> > > print 'GetMccMnc', self.modem.get_state()
> > > return MccMnc
> > >
> > > What I'm seeing is that, even though mmcli shows the modem
> > > getting to
> > > 'connected', this function continues to just get a state of
> > > 'MM_MODEM_STATE_ENABLING'.
> > > If I (re)run the script after the modem has got connected, then
> > > it's
> > > working
> > > as expected.
> > >
> > > I'm not sure if this in an error in my python oop, or incorrect
> > > use
> > > of
> > > libmm. Why aren't the repeated calls to get_state() seeing the
> > > change
> > > of
> > > state?
> >
> > Internally all this stuff is still an event-based system listening
> > on a
> > socket for data from ModemManager. Which means you have to let that
> > code do some work to handle those events at some point, and the way
> > that happens is by letting the GLib mainloop run periodically.
> >
> > What I'd suggest is to do your polling and work from a GLib
> > timeout.
> > That lets the mainloop run, and it lets your code essentially poll
> > in a
> > simple way without doing callbacks and signals:
> >
> > #!/usr/bin/env python
> > import gi
> > gi.require_version('ModemManager', '1.0')
> > from gi.repository import GLib, GObject, Gio, ModemManager
> > import sys, signal
> >
> > main_loop = None
> > watcher = None
> >
> > def signal_handler(data):
> > main_loop.quit()
> >
> > def get_modem_state(obj):
> > modem = obj.get_modem()
> > print "State: %s" %
> > ModemManager.ModemState.get_string(modem.get_state())
> >
> > # Do whatever other work you need to do, as long as it doesn't take
> > many seconds
> >
> > # True = continue running this timeout
> > return True
> >
> > main_loop = GLib.MainLoop()
> > GLib.unix_signal_add(GLib.PRIORITY_HIGH, signal.SIGHUP,
> > signal_handler, None)
> > GLib.unix_signal_add(GLib.PRIORITY_HIGH, signal.SIGTERM,
> > signal_handler, None)
> >
> > connection = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
> > manager = ModemManager.Manager.new_sync(connection,
> > Gio.DBusObjectManagerClientFlags.DO_NOT_AUTO_START,
> > None)
> > modems = manager.get_objects()
> > if len(modems) == 0:
> > print "no modems found"
> > sys.exit(1)
> >
> > GLib.timeout_add_seconds(5, get_modem_state, modems[0])
> > try:
> > main_loop.run()
> > except KeyboardInterrupt:
> > pass
> >
> > Dan
> >
>
> Thanks for the input, Dan.
> If I'm reading you right, you're suggesting having a Glib mainloop
> that runs on - and catches - the MM signals/events, and then use
> get_modem_state() to be able to peek into the status of things?
You got it.
> I did try to read up on the loop stuff, but found it hard going to
> understand [aka find usable documentation on] - which is why I was
> trying to simplify it by being all dumbly-polled ;)
> e.g. As a general question: what is *actually* executed by a
> GLib.MainLoop() call - I kinda thought there must surely be a
> function name that you'd pass it to tell it *what* to 'loop around'.
> Is it just some intrinsic function that's invoked?
The mainloop just waits for events to happen and dispatches them to
handlers. These are things like Unix signals, I/O available on a
socket (network, files, IPC, etc), timer events (timeout_add and
idle_add), things like that.
So you set up handlers for the events you care about, then you tell the
mainloop to run. Your handlers get called on those events and you do
work.
> In the bigger scheme of things, I'm parsing the Operator Code and
> then using it to modify the appropriate NetworkManager connection
> properties - which in itself involves waiting till NM is present 'n
> ready! Hence probably gonna be an issue in view of your "Do whatever
> other work you need to do, **as long as it doesn't take many
> seconds**" comment?
Honestly in your case, it's not going to be a huge problem. You just
won't get any updates to the Modem properties until you return from
your work code. If you don't care much about getting changes soon
after they occur (eg, you're not updating a UI or anything) then you
might not care that what you're doing takes 30 seconds.
But you could also add the NM-ready thing to your timeout/polling
function too, then you don't have to wait. See below...
> Which leaves me puzzling further how to tie the elements together...
> :S
> Perhaps I can have multiple things ['threads' in a sense, even if not
> strictly so] hung off invocations of GLib.timeout_add_seconds(), and
> have them each monitor, maybe through globals [again, taking a dirty
> approach rather than signals..!] what each other is coming up with?
The two major patterns for doing all of this are (a) threads and (b)
event-driven programming. GLib is an example of (b), and it really
does remove the error-prone issues of lock management, concurrent data
access, and deadlocks from the equation. In return, you get to make
sure you clean up your event handlers when you're not using them :)
You can add as many timeout_add_seconds() as you like, or you can put
most of your logic into the one. Globals will work precisely because
you aren't doing multiple threads and thus you are sure that there is
only one thing accessing the global data at a given time. But globals
have their downsides too (mainly from a readability and refactor-
ability perspective).
There's always the signals option too, if you want to go fully event-
driven. They have nothing to do with the mainloop BTW. But they also
break up the program flow and if you have long chains of them, it can
become a bit more confusing.
But back to your original question, here's an example of getting NM
state too:
#!/usr/bin/env python
import gi
gi.require_version('ModemManager', '1.0')
gi.require_version('NM', '1.0')
from gi.repository import GLib, GObject, Gio, ModemManager, NM
import sys, signal
main_loop = None
def signal_handler(data):
main_loop.quit()
def get_modem_state(data):
(obj, nm) = data
modem = obj.get_modem()
print "Modem State: %s" % ModemManager.ModemState.get_string(modem.get_state())
print "NM state: %s" % nm.get_nm_running()
# Do whatever other work you need to do, as long as it doesn't take many seconds
# True = continue running this timeout
return True
main_loop = GLib.MainLoop()
GLib.unix_signal_add(GLib.PRIORITY_HIGH, signal.SIGHUP, signal_handler, None)
GLib.unix_signal_add(GLib.PRIORITY_HIGH, signal.SIGTERM, signal_handler, None)
connection = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
manager = ModemManager.Manager.new_sync(connection,
Gio.DBusObjectManagerClientFlags.DO_NOT_AUTO_START,
None)
modems = manager.get_objects()
if len(modems) == 0:
print "no modems found"
sys.exit(1)
nm = NM.Client.new(None)
GLib.timeout_add_seconds(5, get_modem_state, (modems[0], nm))
try:
main_loop.run()
except KeyboardInterrupt:
pass
More information about the ModemManager-devel
mailing list