VulpesOS/tundra/tools/inspect-hardware.py
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

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))