Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
93 lines
3.8 KiB
Python
93 lines
3.8 KiB
Python
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: MPL-2.0
|
|
"""Read readiness, never dial, send messages, enter PINs or open camera nodes."""
|
|
import json
|
|
from pathlib import Path
|
|
import subprocess
|
|
import time
|
|
|
|
|
|
def command(args):
|
|
try:
|
|
result = subprocess.run(args, capture_output=True, text=True, timeout=12)
|
|
if result.returncode:
|
|
return None
|
|
return result.stdout
|
|
except (OSError, subprocess.TimeoutExpired):
|
|
return None
|
|
|
|
|
|
def bus(*args):
|
|
text = command(['busctl', '--system', '--json=short', *args])
|
|
try:
|
|
return json.loads(text)['data'] if text is not None else None
|
|
except (ValueError, KeyError, TypeError):
|
|
return None
|
|
|
|
|
|
def properties(interface):
|
|
data = bus('call', 'org.ofono', '/ril_0', interface, 'GetProperties')
|
|
if isinstance(data, list) and len(data) == 1:
|
|
data = data[0]
|
|
return data if isinstance(data, dict) else {}
|
|
|
|
|
|
def selected(values, names):
|
|
# GetProperties returns identifiers too: none are copied into the report.
|
|
return {key: values[key].get('data') for key in names
|
|
if key in values and isinstance(values[key], dict)}
|
|
|
|
|
|
def inspect():
|
|
native = 'tundra.rescue=1' in Path('/proc/cmdline').read_text()
|
|
report = {'recordedAtUnix': time.time(), 'environment': 'tundra' if native else 'reference',
|
|
'scope': 'Read-only readiness, not functional call/SMS/audio/camera qualification',
|
|
'services': {}}
|
|
for service in ('tundra-hal', 'tundra-wifi', 'NetworkManager', 'ofono', 'ModemManager'):
|
|
report['services'][service] = (command(['systemctl', 'is-active', service]) or 'inactive').strip()
|
|
modem = selected(properties('org.ofono.Modem'), ('Powered', 'Online', 'Interfaces'))
|
|
sim = selected(properties('org.ofono.SimManager'), ('Present', 'PinRequired', 'LockedPins'))
|
|
registration = selected(properties('org.ofono.NetworkRegistration'), ('Status', 'Technology', 'Strength'))
|
|
interfaces = modem.get('Interfaces') or []
|
|
report['telephony'] = {'modem': modem, 'sim': sim, 'registration': registration,
|
|
'voiceInterface': 'org.ofono.VoiceCallManager' in interfaces,
|
|
'smsInterface': 'org.ofono.MessageManager' in interfaces,
|
|
'outgoingOperationsPerformed': False}
|
|
report['audio'] = {}
|
|
# Run as the session user for access to their PulseAudio server.
|
|
for kind in ('sinks', 'sources'):
|
|
raw = command(['pactl', '--format=json', 'list', kind])
|
|
try:
|
|
items = json.loads(raw) if raw else None
|
|
if items is None:
|
|
short = command(['pactl', 'list', 'short', kind])
|
|
items = [] if short is not None else None
|
|
for line in (short or '').splitlines():
|
|
fields = line.split('\t')
|
|
if len(fields) >= 5:
|
|
items.append({'name': fields[1], 'state': fields[-1],
|
|
'driver': fields[2]})
|
|
if items is None:
|
|
report['audio'][kind] = None
|
|
continue
|
|
report['audio'][kind] = [{key: item.get(key) for key in
|
|
('name', 'state', 'mute', 'active_port', 'driver')}
|
|
for item in items]
|
|
except (TypeError, ValueError):
|
|
report['audio'][kind] = []
|
|
report['camera'] = {
|
|
'halProbeOnly': True,
|
|
'applicationInstalled': Path('/usr/bin/droidian-camera').exists(),
|
|
'captureTested': False,
|
|
}
|
|
report['battery'] = {}
|
|
for name in ('capacity', 'status'):
|
|
try:
|
|
report['battery'][name] = Path('/sys/class/power_supply/battery', name).read_text().strip()
|
|
except OSError:
|
|
pass
|
|
return report
|
|
|
|
|
|
if __name__ == '__main__':
|
|
print(json.dumps(inspect(), indent=2))
|