Skip to content

Commit

Permalink
modemmanager: Allow to add cell broadcast messages
Browse files Browse the repository at this point in the history
Doing userspace development for cell broadcast messages can be tricky as
they're very rare in the wild. If one doesn't mock ModemManager one
needs to have a small GSM cell for testing.

Hence add a AddCbm() message to add cell broadcast messages to the MM
mock.

```
  mm_server.obj.AddCbm(2, 4383, f"This is a test)
```

Messages can be deleted via

```
  busctl call --system org.freedesktop.ModemManager1 /org/freedesktop/ModemManager1/Modems/8 org.freedesktop.ModemManager1.Modem.CellBroadcast Delete o /org/freedesktop/ModemManager1/Cbm/5
```

and listed via

```
  busctl call --system org.freedesktop.ModemManager1 /org/freedesktop/ModemManager1/Modems/8 org.freedesktop.ModemManager1.Modem.CellBroadcast List
```

The implemented API is sufficient to allow MMs `mmcbmmonitor` to detect
and display CBMs and also allows to test Phosh.

Signed-off-by: Guido Günther <[email protected]>
  • Loading branch information
agx authored and martinpitt committed Dec 19, 2024
1 parent a2a4f64 commit 11e97db
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 1 deletion.
82 changes: 81 additions & 1 deletion dbusmock/templates/modemmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@
IS_OBJECT_MANAGER = False
MODEM_IFACE = "org.freedesktop.ModemManager1.Modem"
MODEM_3GPP_IFACE = "org.freedesktop.ModemManager1.Modem.Modem3gpp"
MODEM_CELL_BROADCAST_IFACE = "org.freedesktop.ModemManager1.Modem.CellBroadcast"
MODEM_VOICE_IFACE = "org.freedesktop.ModemManager1.Modem.Voice"
SIM_IFACE = "org.freedesktop.ModemManager1.Sim"
CBM_IFACE = "org.freedesktop.ModemManager1.Cbm"
SIMPLE_MODEM_PATH = "/org/freedesktop/ModemManager1/Modems/8"


class MMModemMode:
Expand Down Expand Up @@ -123,6 +126,31 @@ def load(mock, parameters):
mock.AddMethod(OBJECT_MANAGER_IFACE, "GetManagedObjects", "", "a{oa{sa{sv}}}", code)


def listCbm(_):
paths = []
for path in mockobject.objects:
if path.startswith("/org/freedesktop/ModemManager1/Cbm/"):
paths.append(dbus.ObjectPath(path))
return paths


def deleteCbm(self, cbm_path):
obj = mockobject.objects.get(cbm_path)
if obj is None:
return

modem_obj = mockobject.objects[SIMPLE_MODEM_PATH]
self.RemoveObject(cbm_path)
modem_obj.EmitSignal(
MODEM_CELL_BROADCAST_IFACE,
"Deleted",
"o",
[
cbm_path,
],
)


@dbus.service.method(MOCK_IFACE, in_signature="", out_signature="ss")
def AddSimpleModem(self):
"""Convenience method to add a simple Modem object
Expand All @@ -131,7 +159,7 @@ def AddSimpleModem(self):
Returns the new object path.
"""
modem_path = "/org/freedesktop/ModemManager1/Modems/8"
modem_path = SIMPLE_MODEM_PATH
sim_path = "/org/freedesktop/ModemManager1/SIM/2"
manager = mockobject.objects[MAIN_OBJ]

Expand Down Expand Up @@ -166,6 +194,16 @@ def AddSimpleModem(self):
modem = mockobject.objects[modem_path]
modem.AddProperties(MODEM_3GPP_IFACE, modem_3gpp_props)

modem_cell_broadcast_props = {
"CellBroadcasts": dbus.Array([], signature="o"),
}
modem_cell_broadcast_methods = [
("List", "", "ao", listCbm),
("Delete", "o", "", deleteCbm),
]
modem.AddProperties(MODEM_CELL_BROADCAST_IFACE, modem_cell_broadcast_props)
modem.AddMethods(MODEM_CELL_BROADCAST_IFACE, modem_cell_broadcast_methods)

modem_voice_props = {
"Calls": dbus.Array([], signature="o"),
"EmergencyOnly": False,
Expand All @@ -188,6 +226,7 @@ def AddSimpleModem(self):
{
MODEM_IFACE: modem_props,
MODEM_3GPP_IFACE: modem_3gpp_props,
MODEM_CELL_BROADCAST_IFACE: modem_cell_broadcast_props,
MODEM_VOICE_IFACE: modem_voice_props,
},
],
Expand All @@ -201,3 +240,44 @@ def AddSimpleModem(self):
self.AddObject(sim_path, SIM_IFACE, sim_props, [])

return (modem_path, sim_path)


@dbus.service.method(MOCK_IFACE, in_signature="uus", out_signature="s")
def AddCbm(self, state, channel, text):
"""Convenience method to add a cell broadcast message
Returns the new object path.
"""
n = 1
while mockobject.objects.get(f"/org/freedesktop/ModemManager1/Cbm/{n}") is not None:
n += 1
cbm_path = f"/org/freedesktop/ModemManager1/Cbm/{n}"

cbm_props = {
"State": dbus.UInt32(state),
"Channel": dbus.UInt32(channel),
"Text": dbus.String(text),
"MessageCode": dbus.UInt32(0),
"Update": dbus.UInt32(0),
}
self.AddObject(cbm_path, CBM_IFACE, cbm_props, [])

modem_obj = mockobject.objects[SIMPLE_MODEM_PATH]
paths = listCbm(self)
modem_obj.UpdateProperties(
MODEM_CELL_BROADCAST_IFACE,
{
"CellBroadcasts": dbus.Array(paths),
},
)

modem_obj.EmitSignal(
MODEM_CELL_BROADCAST_IFACE,
"Added",
"o",
[
dbus.ObjectPath(cbm_path),
],
)

return cbm_path
16 changes: 16 additions & 0 deletions tests/test_modemmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@

dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)

mmcli_has_cbm_support = False
have_mmcli = shutil.which("mmcli")
if have_mmcli:
out = subprocess.run(["mmcli", "--help"], capture_output=True, text=True) # pylint: disable=subprocess-run-check
mmcli_has_cbm_support = "--help-cell-broadcast" in out.stdout


class TestModemManagerBase(dbusmock.DBusTestCase):
Expand Down Expand Up @@ -136,6 +140,18 @@ def test_voice_status(self):
self.run_mmcli(["-m", "any", "--voice-status"])
self.assertOutputContainsLine("emergency only: no\n")

@unittest.skipUnless(mmcli_has_cbm_support, "mmcli has no CBM suppot")
def test_cbm(self):
self.p_obj.AddSimpleModem()
self.p_obj.AddCbm(2, 4383, "This is a test")
self.run_mmcli(["-m", "any", "--cell-broadcast-list-cbm"])
self.assertOutputEquals(
[
" /org/freedesktop/ModemManager1/Cbm/1 (received)",
"",
]
)


if __name__ == "__main__":
# avoid writing to stderr
Expand Down

0 comments on commit 11e97db

Please sign in to comment.