mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
modem: route NMEA from cellular modems to gpsd
Cellular modems (Quectel EM05/EM06/EM12, Sierra EM7565, ...) expose
GPS data as NMEA on a dedicated USB serial interface. Until now we
relied on ModemManager's --location-enable-gps-nmea, which makes MM
hold the NMEA port and parse the stream itself — useful for mmcli
but a dead end for the rest of the system. The result was that
modem GPS was visible only via 'show modem' and could not act as an
NTP fallback time source on devices without dedicated GPS hardware.
This commit reroutes the NMEA stream into the existing
gpsd → chronyd pipeline so cellular modems behave just like any
other NMEA-over-USB GPS dongle. No changes to gpsd, chronyd, or
the ietf-hardware:gps component — the user activates it the same
way as for a dedicated receiver, by adding a 'gps' hardware
component that references /dev/gpsN.
This is Phase 2 of the modem GPS / location integration plan
(.notes/modem-gps-plan.md).
udev (src/modemd/77-mm-modem-gps.rules):
- For known vendor:product:interface tuples, set ID_MM_PORT_IGNORE
so ModemManager keeps off the NMEA port, and add
SYMLINK+="gps%n" so gpsd's existing udev hook picks up the
device automatically.
- Interface-number match uses ENV{ID_USB_INTERFACE_NUM} rather
than ATTRS{bInterfaceNumber}: udev requires all ATTRS{} matches
in a rule to share one parent, but idVendor/idProduct live on
the USB device while bInterfaceNumber lives on the USB
interface. ENV{} matching has no parent constraint and udev
already populates ID_USB_INTERFACE_NUM from bInterfaceNumber.
- Initial entries: Quectel EM05 (2c7c:0125), EM06-E (2c7c:0306),
EM12-G (2c7c:0512), Sierra Wireless EM7565 (1199:9091).
Easy to extend.
modemd (modemd/__init__.py):
- GPS_AT_COMMANDS table maps manufacturer -> (enable, disable) AT
command pair. prepare_location() issues the vendor AT command
via 'mmcli --command=AT+QGPS=1' (or AT+CGPS=1 for Sierra)
instead of --location-enable-gps-{nmea,raw}. Vendor lookup is
substring-based ('Quectel' matches both 'Quectel' and 'Quectel
Incorporated') because ModemManager normalises differently
across firmware revisions. For unrecognised vendors the
high-level MM path is still used as a fallback, so nothing
regresses for hardware not yet in the table.
- The AT path runs based on the udev/vendor table, not on MM's
--location-status capabilities. Setting ID_MM_PORT_IGNORE on
the NMEA port removes 'gps' from MM's capability list even
though the GPS hardware is still there, so a capability gate
would mistakenly skip GPS for the very modems we're targeting.
- _location_capabilities() filters MM-managed sources so a single
unsupported flag (e.g. CDMA on an LTE-only modem) doesn't fail
the whole batched call. Sources the modem doesn't support are
silently skipped; a warning is logged only if the user's YANG
config explicitly lists one.
- Subprocess fan-out collapsed: agps-msa, agps-msb, 3gpp and cdma
flags share a single batched mmcli invocation.
prepare_location() drops from 5 subprocess calls to 1 (unknown
vendor) or 2 (known vendor, AT + batched MM).
- Stringly-typed if/elif source dispatch replaced with a
dict-driven table (LOCATION_MM_FLAGS).
- Two helpers eliminate the ['mmcli', '-m', self.path, ...]
boilerplate at ~30 ModemThread call sites:
self._mmcli(*args, check=True) # runcmd wrapper
self._mmclij(*args) # runcmdj wrapper
build (package/feature-modem/Config.in):
- Select BR2_PACKAGE_MODEM_MANAGER_ATVIADBUS so ModemManager
accepts raw AT commands over D-Bus without being started in
--debug mode. Required by the AT path above; without it
'mmcli --command' is rejected with MM_CORE_ERROR_UNAUTHORIZED.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
@@ -2,6 +2,7 @@ config BR2_PACKAGE_FEATURE_MODEM
|
||||
bool "Feature Modem"
|
||||
select BR2_PACKAGE_MODEMD
|
||||
select BR2_PACKAGE_MODEM_MANAGER
|
||||
select BR2_PACKAGE_MODEM_MANAGER_ATVIADBUS
|
||||
select BR2_PACKAGE_MODEM_MANAGER_LIBMBIM
|
||||
select BR2_PACKAGE_MODEM_MANAGER_LIBQMI
|
||||
select BR2_PACKAGE_MODEM_MANAGER_LIBQRTR
|
||||
@@ -10,6 +11,11 @@ config BR2_PACKAGE_FEATURE_MODEM
|
||||
the modemd management daemon. Includes drivers for common
|
||||
USB option modems and QMI/MBIM-based devices.
|
||||
|
||||
ATVIADBUS allows raw AT commands over D-Bus (used by modemd to
|
||||
enable GPS NMEA output via vendor-specific AT commands such as
|
||||
AT+QGPS=1) without needing to start ModemManager in --debug
|
||||
mode.
|
||||
|
||||
config BR2_PACKAGE_FEATURE_MODEM_QUALCOMM
|
||||
bool "Qualcomm-based modems (QMI/QRTR/MHI)"
|
||||
depends on BR2_PACKAGE_FEATURE_MODEM
|
||||
|
||||
@@ -47,6 +47,7 @@ define MODEMD_INSTALL_TARGET_CMDS
|
||||
install -m 644 $(MODEMD_DIR)/modemd.rules $(TARGET_DIR)/lib/udev/rules.d/90-modemd.rules
|
||||
install -m 644 $(MODEMD_DIR)/qmi-wwan-ids.rules $(TARGET_DIR)/lib/udev/rules.d/91-qmi-wwan-ids.rules
|
||||
install -m 644 $(MODEMD_DIR)/77-mm-dell-port-types.rules $(TARGET_DIR)/etc/udev/rules.d/77-mm-dell-port-types.rules
|
||||
install -m 644 $(MODEMD_DIR)/77-mm-modem-gps.rules $(TARGET_DIR)/etc/udev/rules.d/77-mm-modem-gps.rules
|
||||
install -D -m 644 $(MODEMD_DIR)/modemd.modules-load $(TARGET_DIR)/etc/modules-load.d/modemd.conf
|
||||
install -m 755 $(MODEMD_DIR)/modem-command $(TARGET_DIR)/sbin/modem-command
|
||||
ln -sf /usr/libexec/modemd/modemd $(TARGET_DIR)/sbin/modemd
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# Cellular modem NMEA port routing
|
||||
#
|
||||
# For modems whose GPS NMEA stream is available on a dedicated USB
|
||||
# interface, expose that TTY as /dev/gpsN so the existing gpsd → chronyd
|
||||
# pipeline can consume it like any other NMEA USB GPS receiver. Setting
|
||||
# ID_MM_PORT_IGNORE=1 keeps ModemManager from claiming the NMEA port for
|
||||
# itself; modemd issues the vendor-specific AT command to enable GPS
|
||||
# output (see prepare_location() in modemd).
|
||||
#
|
||||
# When adding a new modem entry, find the NMEA interface number with
|
||||
# 'mmcli -m N' or 'lsusb -v' and add a rule below.
|
||||
|
||||
ACTION!="add|change|move|bind", GOTO="modem_gps_end"
|
||||
|
||||
# Note: bInterfaceNumber lives on the USB interface device while
|
||||
# idVendor/idProduct live on the USB device — udev's ATTRS{} requires
|
||||
# all matches in one rule to share a parent, so we match the interface
|
||||
# number via ENV{ID_USB_INTERFACE_NUM}, which udev populates from
|
||||
# bInterfaceNumber and which has no same-parent constraint.
|
||||
|
||||
# Quectel EM05 — NMEA on interface 1
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", \
|
||||
ENV{ID_USB_INTERFACE_NUM}=="01", \
|
||||
ENV{ID_MM_PORT_IGNORE}="1", SYMLINK+="gps%n"
|
||||
|
||||
# Quectel EM06-E / EM06-A — NMEA on interface 1
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0306", \
|
||||
ENV{ID_USB_INTERFACE_NUM}=="01", \
|
||||
ENV{ID_MM_PORT_IGNORE}="1", SYMLINK+="gps%n"
|
||||
|
||||
# Quectel EM12-G — NMEA on interface 1
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0512", \
|
||||
ENV{ID_USB_INTERFACE_NUM}=="01", \
|
||||
ENV{ID_MM_PORT_IGNORE}="1", SYMLINK+="gps%n"
|
||||
|
||||
# Sierra Wireless EM7565 — NMEA on interface 2
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="1199", ATTRS{idProduct}=="9091", \
|
||||
ENV{ID_USB_INTERFACE_NUM}=="02", \
|
||||
ENV{ID_MM_PORT_IGNORE}="1", SYMLINK+="gps%n"
|
||||
|
||||
LABEL="modem_gps_end"
|
||||
+140
-79
@@ -22,6 +22,27 @@ debug = False
|
||||
threads = []
|
||||
reload_event = threading.Event()
|
||||
|
||||
# Vendor-specific AT commands to enable / disable raw NMEA output on the
|
||||
# modem's dedicated GPS port. Used when the udev rules in
|
||||
# 77-mm-modem-gps.rules have set ID_MM_PORT_IGNORE on that port — gpsd
|
||||
# then reads NMEA directly from /dev/gpsN. For unrecognised vendors,
|
||||
# prepare_location() falls back to ModemManager's high-level options
|
||||
# (which keeps MM in charge of the NMEA port). Keys match the
|
||||
# manufacturer string ModemManager reports verbatim.
|
||||
GPS_AT_COMMANDS = {
|
||||
"Quectel": ("AT+QGPS=1", "AT+QGPS=0"),
|
||||
"Sierra Wireless": ("AT+CGPS=1", "AT+CGPS=0"),
|
||||
}
|
||||
|
||||
# ModemManager --location-{enable,disable}-* flag suffixes per source.
|
||||
# 'gps' is handled separately because known vendors take the raw AT path.
|
||||
LOCATION_MM_FLAGS = {
|
||||
"agps-msa": ("agps-msa",),
|
||||
"agps-msb": ("agps-msb",),
|
||||
"3gpp": ("3gpp",),
|
||||
"cdma": ("cdma-bs",),
|
||||
}
|
||||
|
||||
syslog.openlog(logoption=syslog.LOG_PID, facility=syslog.LOG_SYSLOG)
|
||||
|
||||
class Trigger(enum.Enum):
|
||||
@@ -515,6 +536,12 @@ class ModemThread(threading.Thread):
|
||||
def fatal(self, msg):
|
||||
fatal("%s%s" % (self.prefix, msg))
|
||||
|
||||
def _mmcli(self, *args, check=True):
|
||||
return runcmd(["mmcli", "-m", self.path, *args], check=check)
|
||||
|
||||
def _mmclij(self, *args):
|
||||
return runcmdj(["mmcli", "-J", "-m", self.path, *args])
|
||||
|
||||
def init(self):
|
||||
self.timeout = 1
|
||||
self.path = None
|
||||
@@ -628,12 +655,10 @@ class ModemThread(threading.Thread):
|
||||
self.pulldown()
|
||||
|
||||
wait = 0
|
||||
if runcmd(["mmcli", "-m", self.path,
|
||||
"--reset"]):
|
||||
if self._mmcli("--reset"):
|
||||
self.info("Performed a normal reset")
|
||||
wait = 5
|
||||
elif runcmd(["mmcli", "-m", self.path,
|
||||
"--factory-reset=000000"]):
|
||||
elif self._mmcli("--factory-reset=000000"):
|
||||
self.info("Performed a factory reset")
|
||||
wait = 10
|
||||
else:
|
||||
@@ -667,15 +692,14 @@ class ModemThread(threading.Thread):
|
||||
if not self.update():
|
||||
return bearers
|
||||
for path in self.status.get("bearers", []):
|
||||
output = runcmdj(["mmcli", "-J", "-m", self.path, "-b", path])
|
||||
output = self._mmclij("-b", path)
|
||||
if output and "bearer" in output:
|
||||
bearers.append(output["bearer"])
|
||||
return bearers
|
||||
|
||||
def get_profiles(self):
|
||||
profiles = []
|
||||
output = runcmdj(["mmcli", "-J", "-m", self.path,
|
||||
"--3gpp-profile-manager-list"])
|
||||
output = self._mmclij("--3gpp-profile-manager-list")
|
||||
if not output:
|
||||
return profiles
|
||||
for entry in output["modem"]["3gpp"]["profile-manager"]["list"]:
|
||||
@@ -695,7 +719,7 @@ class ModemThread(threading.Thread):
|
||||
def update(self):
|
||||
if not self.path:
|
||||
return False
|
||||
output = runcmdj(["mmcli", "-J", "-m", self.path])
|
||||
output = self._mmclij()
|
||||
if output:
|
||||
output = output.get("modem", {})
|
||||
output = output.get("generic", None)
|
||||
@@ -888,14 +912,14 @@ class ModemThread(threading.Thread):
|
||||
def poweron(self):
|
||||
if self.status.get("power-state", "") == "off":
|
||||
self.info("Powering on")
|
||||
if not runcmd(["mmcli", "-m", self.path, "--set-power-state-on"]):
|
||||
if not self._mmcli("--set-power-state-on"):
|
||||
self.err("Unable to power on")
|
||||
return False
|
||||
return True
|
||||
|
||||
def disable(self):
|
||||
self.info("Disabling")
|
||||
if not runcmd(["mmcli", "-m", self.path, "--disable"]):
|
||||
if not self._mmcli("--disable"):
|
||||
self.err("Unable to disable")
|
||||
return False
|
||||
else:
|
||||
@@ -908,14 +932,13 @@ class ModemThread(threading.Thread):
|
||||
if not self.path:
|
||||
return False
|
||||
if state == "unknown" or state == "disabled":
|
||||
if not runcmd(["mmcli", "-m", self.path, "--enable"]):
|
||||
if not self._mmcli("--enable"):
|
||||
self.err("Unable to enable")
|
||||
return False
|
||||
return True
|
||||
|
||||
def unlock(self):
|
||||
cmd = ["mmcli", "-m", self.path, "--sim=%s" %
|
||||
self.status.get("sim", "0")]
|
||||
args = ["--sim=%s" % self.status.get("sim", "0")]
|
||||
|
||||
if self.status.get("unlock-required", "") == "sim-pin":
|
||||
self.info("Unlocking with PIN")
|
||||
@@ -923,20 +946,20 @@ class ModemThread(threading.Thread):
|
||||
if not pin:
|
||||
self.err("No PIN defined")
|
||||
return False
|
||||
cmd.append("--pin=%s" % pin)
|
||||
args.append("--pin=%s" % pin)
|
||||
elif self.status.get("unlock-required", "") == "sim-puk":
|
||||
self.info("Unlocking with PUK")
|
||||
puk = self.cfg.get("puk")
|
||||
if not puk:
|
||||
self.err("No PUK defined")
|
||||
return False
|
||||
cmd.append("--puk=%s" % puk)
|
||||
args.append("--puk=%s" % puk)
|
||||
else:
|
||||
self.err("Unsupported lock")
|
||||
return False
|
||||
|
||||
self.disable()
|
||||
if not runcmd(cmd):
|
||||
if not self._mmcli(*args):
|
||||
self.err("Unable to unlock")
|
||||
return False
|
||||
else:
|
||||
@@ -1092,9 +1115,8 @@ class ModemThread(threading.Thread):
|
||||
for p in self.get_profiles():
|
||||
if p["profile-id"] != "1":
|
||||
self.dbg("Deleting profile %s" % p["profile-id"])
|
||||
if not runcmd(["mmcli", "-m", self.path,
|
||||
"--3gpp-profile-manager-delete=%s" %
|
||||
p["profile-id"]]):
|
||||
if not self._mmcli("--3gpp-profile-manager-delete=%s" %
|
||||
p["profile-id"]):
|
||||
self.err("Unable to delete profile")
|
||||
return False
|
||||
|
||||
@@ -1108,20 +1130,17 @@ class ModemThread(threading.Thread):
|
||||
self.err("Max. number of bearers reached")
|
||||
break
|
||||
if bearer["pid"] != "1":
|
||||
if not runcmd(["mmcli", "-m", self.path,
|
||||
"--3gpp-profile-manager-set="]):
|
||||
if not self._mmcli("--3gpp-profile-manager-set="):
|
||||
self.err("Unable to create profile")
|
||||
return False
|
||||
|
||||
args = self.get_profile_args(bearer)
|
||||
if not runcmd(["mmcli", "-m", self.path,
|
||||
"--3gpp-profile-manager-set=%s" % args]):
|
||||
if not self._mmcli("--3gpp-profile-manager-set=%s" % args):
|
||||
self.err("Unable to configure profile")
|
||||
return False
|
||||
|
||||
args = self.get_bearer_args(bearer)
|
||||
if not runcmd(["mmcli", "-m", self.path,
|
||||
"--create-bearer=%s" % args]):
|
||||
if not self._mmcli("--create-bearer=%s" % args):
|
||||
self.err("Unable to create bearer")
|
||||
return False
|
||||
|
||||
@@ -1135,8 +1154,7 @@ class ModemThread(threading.Thread):
|
||||
|
||||
for bearer in bearers:
|
||||
args = self.get_bearer_args(bearer)
|
||||
if not runcmd(["mmcli", "-m", self.path,
|
||||
"--create-bearer=multiplex=required,%s" % args]):
|
||||
if not self._mmcli("--create-bearer=multiplex=required,%s" % args):
|
||||
self.err("Unable to create bearer")
|
||||
return False
|
||||
self.info("Created multiplex bearer for '%s'" % bearer["apn"])
|
||||
@@ -1144,9 +1162,7 @@ class ModemThread(threading.Thread):
|
||||
return True
|
||||
|
||||
def prepare_default_bearer(self, bearer):
|
||||
if not runcmd(["mmcli", "-m", self.path,
|
||||
"--create-bearer=%s" %
|
||||
self.get_bearer_args(bearer)]):
|
||||
if not self._mmcli("--create-bearer=%s" % self.get_bearer_args(bearer)):
|
||||
self.err("Unable to create bearer")
|
||||
return False
|
||||
|
||||
@@ -1160,8 +1176,7 @@ class ModemThread(threading.Thread):
|
||||
# wipe existing bearers
|
||||
for bearer in self.get_bearers():
|
||||
self.dbg("Deleting bearer %s" % bearer["dbus-path"])
|
||||
if not runcmd(["mmcli", "-m", self.path,
|
||||
"--delete-bearer=%s" % bearer["dbus-path"]]):
|
||||
if not self._mmcli("--delete-bearer=%s" % bearer["dbus-path"]):
|
||||
self.err("Unable to delete bearer")
|
||||
return False
|
||||
|
||||
@@ -1169,9 +1184,8 @@ class ModemThread(threading.Thread):
|
||||
initial = self.bearers.get("initial")
|
||||
if initial:
|
||||
self.info("Setting initial bearer '%s'" % initial["apn"])
|
||||
if not runcmd(["mmcli", "-m", self.path,
|
||||
"--3gpp-set-initial-eps-bearer-settings=%s" %
|
||||
self.get_bearer_args(initial)]):
|
||||
if not self._mmcli("--3gpp-set-initial-eps-bearer-settings=%s" %
|
||||
self.get_bearer_args(initial)):
|
||||
self.err("Unable to set initial bearer")
|
||||
|
||||
# configure dialup bearers
|
||||
@@ -1194,8 +1208,7 @@ class ModemThread(threading.Thread):
|
||||
if len(bands) == 0 or "any" in bands:
|
||||
return True
|
||||
|
||||
if not runcmd(["mmcli", "-m", self.path,
|
||||
"--set-current-bands=%s" % "|".join(bands)]):
|
||||
if not self._mmcli("--set-current-bands=%s" % "|".join(bands)):
|
||||
self.err("Unable to set band")
|
||||
return False
|
||||
return True
|
||||
@@ -1212,7 +1225,7 @@ class ModemThread(threading.Thread):
|
||||
allow = []
|
||||
|
||||
if pref == "none" and len(allow) == 0:
|
||||
runcmd(["mmcli", "-m", self.path, "--set-allowed-modes=any"], check=False)
|
||||
self._mmcli("--set-allowed-modes=any", check=False)
|
||||
return True
|
||||
|
||||
mode = "allowed: %s; preferred: %s" % (", ".join(allow), pref)
|
||||
@@ -1232,12 +1245,47 @@ class ModemThread(threading.Thread):
|
||||
|
||||
self.info("Setting mode '%s'" % mode)
|
||||
|
||||
cmd = ["mmcli", "-m", self.path]
|
||||
args = []
|
||||
if len(allow) > 0:
|
||||
cmd.append("--set-allowed-modes=%s" % "|".join(allow))
|
||||
args.append("--set-allowed-modes=%s" % "|".join(allow))
|
||||
if pref != "none":
|
||||
cmd.append("--set-preferred-mode=%s" % pref)
|
||||
return runcmd(cmd)
|
||||
args.append("--set-preferred-mode=%s" % pref)
|
||||
return self._mmcli(*args)
|
||||
|
||||
def _gps_at_command(self, enable):
|
||||
"""Vendor-specific AT command to toggle GPS NMEA, or None.
|
||||
|
||||
Match by substring so 'Quectel' picks up 'Quectel Incorporated' too —
|
||||
ModemManager normalises differently across firmware revisions.
|
||||
"""
|
||||
for vendor, cmds in GPS_AT_COMMANDS.items():
|
||||
if vendor in self.manf:
|
||||
return cmds[0] if enable else cmds[1]
|
||||
return None
|
||||
|
||||
def _location_capabilities(self):
|
||||
"""YANG source names this modem actually supports.
|
||||
|
||||
Returns None if the capability query itself fails. Otherwise a
|
||||
set drawn from {gps, agps-msa, agps-msb, 3gpp, cdma}. Asking
|
||||
ModemManager to enable an unsupported source fails the whole
|
||||
batched mmcli call, so prepare_location() filters against this.
|
||||
"""
|
||||
output = self._mmclij("--location-status")
|
||||
if not output:
|
||||
return None
|
||||
caps = output.get("modem", {}).get("location", {}).get("capabilities", "")
|
||||
if isinstance(caps, str):
|
||||
caps = [c.strip() for c in caps.split(",") if c.strip()]
|
||||
mapping = {
|
||||
"3gpp-lac-ci": "3gpp",
|
||||
"gps-raw": "gps",
|
||||
"gps-nmea": "gps",
|
||||
"agps-msa": "agps-msa",
|
||||
"agps-msb": "agps-msb",
|
||||
"cdma-bs": "cdma",
|
||||
}
|
||||
return {mapping[c] for c in caps if c in mapping}
|
||||
|
||||
def prepare_location(self):
|
||||
self.info("Preparing location")
|
||||
@@ -1246,34 +1294,53 @@ class ModemThread(threading.Thread):
|
||||
if not self.location["enabled"]:
|
||||
return True
|
||||
|
||||
for source in ["gps", "agps-msa", "agps-msb", "3gpp", "cdma"]:
|
||||
if self.location["enabled"] and source in self.location["sources"]:
|
||||
sources = self.location["sources"]
|
||||
|
||||
# GPS via vendor AT command (known vendor) or via MM (fallback).
|
||||
# The AT path doesn't consult MM's --location-status, since
|
||||
# marking the NMEA port ID_MM_PORT_IGNORE removes 'gps' from MM's
|
||||
# capability list even though the hardware is still there.
|
||||
at = self._gps_at_command("gps" in sources)
|
||||
if at is not None:
|
||||
if not self._mmcli("--command=%s" % at):
|
||||
self.err("Unable to send AT command '%s'" % at)
|
||||
self.location["state"] = "failed"
|
||||
return False
|
||||
gps_flags = ()
|
||||
else:
|
||||
gps_flags = ("gps-nmea", "gps-raw")
|
||||
|
||||
# Capability filter for the remaining (MM-managed) sources so
|
||||
# CDMA on an LTE-only modem doesn't fail the whole batched call.
|
||||
caps = self._location_capabilities()
|
||||
if caps is None:
|
||||
self.err("Unable to query modem location capabilities")
|
||||
self.location["state"] = "failed"
|
||||
return False
|
||||
|
||||
flag_map = {**LOCATION_MM_FLAGS, "gps": gps_flags}
|
||||
args = []
|
||||
for source, flags in flag_map.items():
|
||||
if source == "gps" and at is not None:
|
||||
continue # AT command already handled GPS
|
||||
if source not in caps:
|
||||
if source in sources:
|
||||
self.err("Modem does not support location source"
|
||||
" '%s', skipping" % source)
|
||||
continue
|
||||
enabled = source in sources
|
||||
if enabled:
|
||||
self.info("Enabling location source '%s'" % source)
|
||||
action = "enable"
|
||||
else:
|
||||
self.dbg("Disabling location source '%s'" % source)
|
||||
action = "disable"
|
||||
action = "enable" if enabled else "disable"
|
||||
for flag in flags:
|
||||
args.append("--location-%s-%s" % (action, flag))
|
||||
|
||||
args = []
|
||||
if source == "gps":
|
||||
args = ["--location-%s-gps-nmea" % action,
|
||||
"--location-%s-gps-raw" % action]
|
||||
elif source == "agps-msa":
|
||||
args = ["--location-%s-agps-msa" % action]
|
||||
elif source == "agps-msb":
|
||||
args = ["--location-%s-agps-msb" % action]
|
||||
elif source == "3gpp":
|
||||
args = ["--location-%s-3gpp" % action]
|
||||
elif source == "cdma":
|
||||
args = ["--location-%s-cdma-bs" % action]
|
||||
else:
|
||||
continue
|
||||
|
||||
if not runcmd(["mmcli", "-m", self.path] + args):
|
||||
self.err("Unable to %s location source" % action)
|
||||
if self.location["enabled"]:
|
||||
self.location["state"] = "failed"
|
||||
return False
|
||||
if args and not self._mmcli(*args):
|
||||
self.err("Unable to configure location sources")
|
||||
self.location["state"] = "failed"
|
||||
return False
|
||||
return True
|
||||
|
||||
def prepare(self):
|
||||
@@ -1301,10 +1368,7 @@ class ModemThread(threading.Thread):
|
||||
def connect(self):
|
||||
for bearer in self.status.get("bearers", []):
|
||||
self.info("Connecting %s" % bearer)
|
||||
if not runcmd(["mmcli", "-m", self.path,
|
||||
"--connect",
|
||||
"--bearer=%s" % bearer
|
||||
]):
|
||||
if not self._mmcli("--connect", "--bearer=%s" % bearer):
|
||||
self.connfail += 1
|
||||
self.err("Unable to connect (%d failures)" % self.connfail)
|
||||
self.err("Reason: %s" % self.get_conn_failure(bearer))
|
||||
@@ -1351,8 +1415,8 @@ class ModemThread(threading.Thread):
|
||||
|
||||
if bearer["status"]["connected"] == "yes":
|
||||
self.info("Disconnecting %s" % bearer["dbus-path"])
|
||||
runcmd(["mmcli", "-m", self.path,
|
||||
"--disconnect", "--bearer=%s" % bearer["dbus-path"]])
|
||||
self._mmcli("--disconnect",
|
||||
"--bearer=%s" % bearer["dbus-path"])
|
||||
for iface in self.ifaces:
|
||||
self.linkdown(iface)
|
||||
|
||||
@@ -1444,8 +1508,7 @@ class ModemThread(threading.Thread):
|
||||
if not self.location["enabled"]:
|
||||
return
|
||||
|
||||
output = runcmdj(["mmcli", "-J", "-m", self.path,
|
||||
"--location-get"])
|
||||
output = self._mmclij("--location-get")
|
||||
loc = (output or {}).get("modem", {}).get("location", {})
|
||||
gps = loc.get("gps", {})
|
||||
gpp = loc.get("3gpp", {})
|
||||
@@ -1511,8 +1574,7 @@ class ModemThread(threading.Thread):
|
||||
|
||||
storage = payload["properties"]["storage"].upper()
|
||||
if storage != "--":
|
||||
if not runcmd(["mmcli", "-m", self.path,
|
||||
"--messaging-delete-sms=%s" % sms["path"]]):
|
||||
if not self._mmcli("--messaging-delete-sms=%s" % sms["path"]):
|
||||
self.err("Unable to delete SMS from %s storage" % storage)
|
||||
else:
|
||||
self.info("Deleted SMS from %s storage" % storage)
|
||||
@@ -1527,12 +1589,11 @@ class ModemThread(threading.Thread):
|
||||
for sms in list_sms(self.path):
|
||||
if sms["type"] == "notsent":
|
||||
self.dbg("Sending sms %s" % sms)
|
||||
runcmd(["mmcli", "-m", self.path, "-s", sms["path"], "--send"])
|
||||
self._mmcli("-s", sms["path"], "--send")
|
||||
|
||||
elif sms["type"] == "sent":
|
||||
self.notif("SMS-SENT")
|
||||
runcmd(["mmcli", "-m", self.path,
|
||||
"--messaging-delete-sms=%s" % sms["path"]])
|
||||
self._mmcli("--messaging-delete-sms=%s" % sms["path"])
|
||||
|
||||
elif sms["type"] == "received":
|
||||
self.save_sms(sms)
|
||||
|
||||
Reference in New Issue
Block a user