diff --git a/carwatch/trips.py b/carwatch/trips.py index cfb42f1..0e83d6e 100644 --- a/carwatch/trips.py +++ b/carwatch/trips.py @@ -13,6 +13,7 @@ from __future__ import annotations +import os import subprocess import time from dataclasses import dataclass @@ -33,16 +34,45 @@ class Event: at: float +def _iw_ssid() -> str | None: + """SSID from `iw dev link` on each wireless interface. The VTA-439 + (27 Sep 2026) had no wireless-tools, so iwgetid did not exist and every + "am I at home" check read False: the dash asked for a password on the + home wifi and trips could not see home. iw ships with the kernel's + wireless stack on Ubuntu and Pi OS alike.""" + try: + ifaces = [n for n in os.listdir("/sys/class/net") + if os.path.isdir(f"/sys/class/net/{n}/wireless")] + except OSError: + return None + for iface in ifaces: + try: + out = subprocess.run(["iw", "dev", iface, "link"], capture_output=True, + text=True, timeout=5).stdout + except Exception: + continue + for line in out.splitlines(): + line = line.strip() + if line.startswith("SSID:"): + ssid = line[len("SSID:"):].strip() + if ssid: + return ssid + return None + + def current_ssid() -> str | None: - """SSID the Pi is associated with, or None. Uses iwgetid (wireless-tools).""" + """SSID the box is associated with, or None. iwgetid (wireless-tools) + first, as installed by install.sh; `iw` when iwgetid is missing.""" try: out = subprocess.run( ["iwgetid", "-r"], capture_output=True, text=True, timeout=5 ) ssid = out.stdout.strip() - return ssid or None + if ssid: + return ssid except Exception: - return None + pass + return _iw_ssid() class TripTracker: diff --git a/install.sh b/install.sh index 0b7e116..9ae2637 100755 --- a/install.sh +++ b/install.sh @@ -71,6 +71,10 @@ echo ">> apt: updating package lists" sudo apt-get update -qq sudo apt-get install -y wireless-tools poppler-utils bluez bluez-alsa-utils \ alsa-utils ffmpeg network-manager rsync curl git >/dev/null +# The voice listener opens /dev/snd, which is group audio. Pi OS puts the first +# user in audio; Ubuntu (the VTA-439, 27 Sep 2026) does not, and the listener +# then cannot open the USB speakerphone at all. +id -nG "$RUN_USER" | grep -qw audio || sudo usermod -aG audio "$RUN_USER" # 5) Every systemd unit, user/home rewritten to yours. for u in "$DEST"/systemd/carwatch-*.service "$DEST"/systemd/carwatch-*.timer; do diff --git a/tests/test_current_ssid.py b/tests/test_current_ssid.py new file mode 100644 index 0000000..1057343 --- /dev/null +++ b/tests/test_current_ssid.py @@ -0,0 +1,50 @@ +"""current_ssid on boxes without wireless-tools (VTA-439, 27 Sep 2026): the +dash asked for a password on the home wifi because iwgetid did not exist. +subprocess and /sys are mocked; nothing touches real networking.""" +import os +import sys +import unittest +from unittest import mock + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +from carwatch import trips # noqa: E402 + +IW_LINK = ("Connected to 3c:7c:3f:34:62:44 (on wlp98s0)\n" + "\tSSID: wifi router\n\tfreq: 5180.0\n") + + +def fake_run(iwgetid=None, iw=IW_LINK): + def run(cmd, **kw): + if cmd[0] == "iwgetid": + if iwgetid is None: + raise FileNotFoundError("iwgetid") + return mock.Mock(stdout=iwgetid) + if cmd[0] == "iw": + return mock.Mock(stdout=iw) + raise AssertionError(cmd) + return run + + +class CurrentSsid(unittest.TestCase): + def _call(self, **kw): + with mock.patch.object(trips.subprocess, "run", side_effect=fake_run(**kw)), \ + mock.patch.object(trips.os, "listdir", return_value=["lo", "wlp98s0"]), \ + mock.patch.object(trips.os.path, "isdir", + side_effect=lambda p: p.endswith("wlp98s0/wireless")): + return trips.current_ssid() + + def test_iwgetid_wins_when_present(self): + self.assertEqual(self._call(iwgetid="Pi Home\n"), "Pi Home") + + def test_falls_back_to_iw_when_iwgetid_missing(self): + self.assertEqual(self._call(iwgetid=None), "wifi router") + + def test_falls_back_to_iw_when_iwgetid_empty(self): + self.assertEqual(self._call(iwgetid=""), "wifi router") + + def test_not_connected_is_none(self): + self.assertIsNone(self._call(iwgetid=None, iw="Not connected.\n")) + + +if __name__ == "__main__": + unittest.main()