feat: FEAT-14 dual T3U — wlan2 uplink, wlan1 AP, onboard wlan0 fallback

This commit is contained in:
Mathieu Moran
2026-08-14 21:32:30 -04:00
parent c7329186f4
commit 25f57603c6
9 changed files with 57 additions and 57 deletions
+17 -16
View File
@@ -33,7 +33,7 @@ dedicated PLAN.md phase (or fold it into the relevant existing work) and mark it
| FEAT-11 | `SHIPPED` | Hotspot SSID/password settings + 5GHz WiFi role swap | Phase 27 | | FEAT-11 | `SHIPPED` | Hotspot SSID/password settings + 5GHz WiFi role swap | Phase 27 |
| FEAT-12 | `SHIPPED` | Log OBD speed before GPS fix (rolling-start capture) | Phase 27 | | FEAT-12 | `SHIPPED` | Log OBD speed before GPS fix (rolling-start capture) | Phase 27 |
| FEAT-13 | `IDEA` | Flush uploaded tracking rows from local DB | unassigned | | FEAT-13 | `IDEA` | Flush uploaded tracking rows from local DB | unassigned |
| FEAT-14 | `IDEA` | Second T3U to replace onboard wlan0 (dual AC radios) | unassigned | | FEAT-14 | `SHIPPED` | Second T3U to replace onboard wlan0 (dual AC radios) | Phase 29 |
| FEAT-15 | `SHIPPED` | Clear DTC codes from the DTC page | Phase 29 | | FEAT-15 | `SHIPPED` | Clear DTC codes from the DTC page | Phase 29 |
--- ---
@@ -259,21 +259,22 @@ dedicated PLAN.md phase (or fold it into the relevant existing work) and mark it
### FEAT-14 — Second T3U to replace onboard wlan0 (dual AC radios) ### FEAT-14 — Second T3U to replace onboard wlan0 (dual AC radios)
- **Status**: `IDEA` - **Status**: `SHIPPED` 2026-08-14
- **Area**: hardware + `deploy/yaris_uplink.py`, `deploy/install_netwatch.sh`, `modules/app_network.py` - **Area**: hardware + `deploy/yaris_uplink.py`, `deploy/install_netwatch.sh`, `modules/app_network.py`, `deploy/90-yaris-uplink`
- **Phase**: unassigned - **Phase**: Phase 29
- **What**: Owner bought a second TP-Link Archer T3U (RTL8812BU) to replace the onboard - **What**: Owner added a second TP-Link Archer T3U (RTL8812BU) to replace the onboard
BCM43438 currently serving as the internet uplink (wlan0). Both radios would then be fast BCM43438 as the internet uplink. Both radios are now AC1300 T3U dongles — `wlan1` (T3U #1)
AC1300 T3U dongles — one AP (wlan1, 5GHz) and one uplink (wlan0) — removing the current hosts the 5GHz in-car AP, `wlan2` (T3U #2) carries the uplink (home-wifi + phone-hotspot),
tradeoff where away-from-home uploads run over the weak onboard radio. and the onboard `wlan0` is retired to a weak fallback. Removes the old tradeoff where
- **Considerations**: away-from-home uploads ran over the weak onboard radio.
- Both dongles share the Pi 3's USB 2.0 bus (~480 Mbps total), so two AC dongles can't both - **Changes**: `PRIMARY_INTERFACE=wlan2` / `FALLBACK_INTERFACE=wlan0` / `AP_INTERFACE=wlan1`
run at full speed simultaneously — but each still far exceeds the onboard radio. in `yaris_uplink.py` + `app_network.py`; `install_netwatch.sh` rebinds `home-wifi` +
- Power: two AC dongles draw more than onboard + one; verify the 5V GPIO feed holds `phone-hotspot` to wlan2 and recreates the `home-wifi-fb` fallback on wlan0; the NM
(`vcgencmd get_throttled` should stay `0x0` for no undervoltage). dispatcher (`90-yaris-uplink`) now listens on `wlan0|wlan1|wlan2`.
- Interface naming: both radios are USB now, so `wlan0`/`wlan1` may not map predictably — - **Verified**: the second T3U enumerates as `wlan2` (rtl88x2bu) and
pin `connection.interface-name` and/or add MAC-based udev rules so AP vs uplink never swap. `vcgencmd get_throttled` = `0x0` (no undervoltage with both dongles powered).
- The rtl88x2bu driver is already built; a second identical dongle should be plug-and-play. - **Open item**: interface-name pinning relies on USB enumeration order; if the two T3Us ever
swap ports, `wlan1`/`wlan2` swap — a MAC-based udev/`.link` rule is the robust follow-up.
### FEAT-15 — Clear DTC codes from the DTC page ### FEAT-15 — Clear DTC codes from the DTC page
+7 -8
View File
@@ -12,14 +12,13 @@ pick up the project without needing prior chat history.
- **Board**: Raspberry Pi 3 Model B Rev 1.2, 906MB RAM. Confirmed too weak to run an AI coding - **Board**: Raspberry Pi 3 Model B Rev 1.2, 906MB RAM. Confirmed too weak to run an AI coding
agent — all agent-assisted dev work happens on a separate local machine; the Pi is only used agent — all agent-assisted dev work happens on a separate local machine; the Pi is only used
for hardware bring-up/testing/deployment via SSH (`yaris@<pi-ip>`). for hardware bring-up/testing/deployment via SSH (`yaris@<pi-ip>`).
- **WiFi**: Dual-radio setup — **TP-Link Archer T3U (RTL8812BU)** USB dongle as `wlan1` and the - **WiFi**: **Dual T3U** setup (2026-08-14, FEAT-14) — two TP-Link Archer T3U (RTL8812BU)
onboard BCM43438 as `wlan0`. **2026-08-12 role swap**: the fast T3U now hosts the in-car AP dongles: `wlan1` hosts the in-car AP ("vroom vroom yaris de course" on 5GHz at 10.42.0.1)
("vroom vroom yaris de course" on 5GHz at 10.42.0.1) for fast phone access (Android dev, video and `wlan2` carries the internet uplink (home WiFi "BuzzBis" + phone hotspot, static IP
browsing); the weak onboard radio carries the internet uplink (home WiFi "BuzzBis" + 192.168.50.220, gw 192.168.50.1). The onboard BCM43438 (`wlan0`) is retired to a weak
phone hotspot, static IP 192.168.50.220, gw 192.168.50.1). Both radios active simultaneously. fallback. Away-from-home uploads (dashcam/trip sync) now run on a fast AC radio. A single
Tradeoff: away-from-home uploads (dashcam/trip sync) now run over the weak onboard radio — a radio cannot be AP + client at once, so the phone-hotspot uplink cannot live on wlan1 while
background task, accepted by owner. A single radio cannot be AP + client at once, so the it hosts the AP.
phone-hotspot uplink cannot also live on wlan1 while it hosts the AP.
- **GPS/GSM**: Waveshare **SIM868 GSM/GPRS/GNSS/Bluetooth HAT**. GNSS provides live GPS fixes - **GPS/GSM**: Waveshare **SIM868 GSM/GPRS/GNSS/Bluetooth HAT**. GNSS provides live GPS fixes
(confirmed working, ~7 satellites, ~80s cold-start TTFF). GSM/GPRS cellular is **not in use** (confirmed working, ~7 satellites, ~80s cold-start TTFF). GSM/GPRS cellular is **not in use**
(no SIM card available); a Huawei **E3372h-153 4G LTE modem** has been purchased as a future (no SIM card available); a Huawei **E3372h-153 4G LTE modem** has been purchased as a future
+1 -1
View File
@@ -131,7 +131,7 @@ def uplink_connect():
return jsonify(error=str(error)), 503 return jsonify(error=str(error)), 503
return jsonify(status="connection_requested"), 202 return jsonify(status="connection_requested"), 202
_HOTSPOT_PROFILE = "Hotspot-local" # wlan0 AP profile name in NetworkManager _HOTSPOT_PROFILE = "Hotspot-local" # AP profile name in NetworkManager (hosted on wlan1)
@app.route('/api/hotspot') @app.route('/api/hotspot')
+1 -1
View File
@@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
case "$1" in case "$1" in
wlan0|wlan1) ;; wlan0|wlan1|wlan2) ;;
*) exit 0 ;; *) exit 0 ;;
esac esac
+11 -11
View File
@@ -7,15 +7,15 @@
# already exist (see PLAN.md Phase 6). The optional "phone-hotspot" profile is # already exist (see PLAN.md Phase 6). The optional "phone-hotspot" profile is
# created through the Settings page. # created through the Settings page.
# #
# Since 2026-08-12: the fast TP-Link T3U (wlan1) hosts the in-car AP # Since 2026-08-14 (FEAT-14): dual T3U — wlan1 (T3U #1) hosts the in-car AP
# ("Hotspot-local"), while the onboard BCM43438 (wlan0) carries the internet # ("Hotspot-local"), and a second T3U (wlan2) carries the internet uplink
# uplink (home-wifi / phone-hotspot). # (home-wifi / phone-hotspot); the onboard BCM43438 (wlan0) is the fallback.
set -euo pipefail set -euo pipefail
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
WLAN=wlan0 # onboard — internet uplink WLAN=wlan2 # T3U #2 — internet uplink
WLAN_FB=wlan1 # T3U — uplink fallback WLAN_FB=wlan0 # onboard BCM43438 — uplink fallback
AP_IFACE=wlan1 # T3U — hosts the in-car AP AP_IFACE=wlan1 # T3U #1 — hosts the in-car AP
sudo systemctl stop yaris-uplink-wait.service 2>/dev/null || true sudo systemctl stop yaris-uplink-wait.service 2>/dev/null || true
sudo systemctl stop yaris-netwatch.timer yaris-netwatch.service 2>/dev/null || true sudo systemctl stop yaris-netwatch.timer yaris-netwatch.service 2>/dev/null || true
@@ -28,9 +28,9 @@ sudo install -m 644 "$REPO_DIR/deploy/yaris-hotspot-dns.conf" \
sudo install -m 755 "$REPO_DIR/deploy/90-yaris-uplink" \ sudo install -m 755 "$REPO_DIR/deploy/90-yaris-uplink" \
/etc/NetworkManager/dispatcher.d/90-yaris-uplink /etc/NetworkManager/dispatcher.d/90-yaris-uplink
# Bind the internet uplink to the onboard radio (wlan0) # Bind the internet uplink to the second T3U (wlan2)
sudo nmcli connection modify home-wifi connection.interface-name "$WLAN" sudo nmcli connection modify home-wifi connection.interface-name "$WLAN"
# The in-car AP lives on the fast T3U (wlan1), forced to 5GHz # The in-car AP lives on T3U #1 (wlan1), forced to 5GHz
sudo nmcli connection modify Hotspot-local \ sudo nmcli connection modify Hotspot-local \
connection.interface-name "$AP_IFACE" \ connection.interface-name "$AP_IFACE" \
802-11-wireless.band a \ 802-11-wireless.band a \
@@ -44,8 +44,8 @@ if nmcli connection show phone-hotspot >/dev/null 2>&1; then
802-11-wireless.mtu 1400 802-11-wireless.mtu 1400
fi fi
# Create wlan1 fallback profile (lower autoconnect-priority so wlan0 wins) # Create wlan0 fallback profile (lower autoconnect-priority so wlan2 wins)
# home-wifi-fb: same SSID as home-wifi but on wlan1 (T3U) # home-wifi-fb: same SSID as home-wifi but on wlan0 (onboard)
HOME_SSID=$(nmcli -g 802-11-wireless.ssid connection show home-wifi 2>/dev/null || true) HOME_SSID=$(nmcli -g 802-11-wireless.ssid connection show home-wifi 2>/dev/null || true)
HOME_PSK=$(sudo nmcli -g 802-11-wireless-security.psk connection show --show-secrets home-wifi 2>/dev/null || true) HOME_PSK=$(sudo nmcli -g 802-11-wireless-security.psk connection show --show-secrets home-wifi 2>/dev/null || true)
if [ -n "$HOME_SSID" ] && [ -n "$HOME_PSK" ]; then if [ -n "$HOME_SSID" ] && [ -n "$HOME_PSK" ]; then
@@ -62,7 +62,7 @@ if [ -n "$HOME_SSID" ] && [ -n "$HOME_PSK" ]; then
echo "Created home-wifi-fb fallback on $WLAN_FB" echo "Created home-wifi-fb fallback on $WLAN_FB"
fi fi
# Re-enable wlan0 as a managed (but low-priority) fallback # Re-enable wlan0 (onboard) as a managed low-priority fallback
sudo nmcli device set "$WLAN_FB" managed yes 2>/dev/null || true sudo nmcli device set "$WLAN_FB" managed yes 2>/dev/null || true
sudo cp "$REPO_DIR/deploy/yaris-netwatch.service" /etc/systemd/system/yaris-netwatch.service sudo cp "$REPO_DIR/deploy/yaris-netwatch.service" /etc/systemd/system/yaris-netwatch.service
+5 -5
View File
@@ -20,7 +20,7 @@ cd /home/yaris/app/yaris-obd
# REQUIRED before netwatch install: # REQUIRED before netwatch install:
# Create/update NetworkManager profiles named exactly: # Create/update NetworkManager profiles named exactly:
# - home-wifi (client WiFi → wlan0, the internet uplink) # - home-wifi (client WiFi → wlan2, the internet uplink)
# - Hotspot-local (local AP, shared at 10.42.0.1/24, on wlan1) # - Hotspot-local (local AP, shared at 10.42.0.1/24, on wlan1)
# See section 4 below for exact nmcli commands. # See section 4 below for exact nmcli commands.
@@ -42,10 +42,10 @@ curl -I http://127.0.0.1/
## TP-Link Archer T3U (RTL8812BU) driver ## TP-Link Archer T3U (RTL8812BU) driver
This project expects the T3U USB adapter as `wlan1` for the in-car AP (5GHz) This project expects two T3U USB adapters — `wlan1` for the in-car AP (5GHz)
and the onboard radio as `wlan0` for the internet uplink (home WiFi + phone and `wlan2` for the internet uplink (home WiFi + phone hotspot); the onboard
hotspot). On a fresh Pi, install the RTL8812BU DKMS driver before configuring `wlan0` is a fallback. On a fresh Pi, install the RTL8812BU DKMS driver before
WiFi profiles: configuring WiFi profiles:
```bash ```bash
sudo apt-get update sudo apt-get update
+7 -7
View File
@@ -12,12 +12,12 @@ import uuid
PHONE_CONNECTION = "phone-hotspot" PHONE_CONNECTION = "phone-hotspot"
HOME_CONNECTION = "home-wifi" HOME_CONNECTION = "home-wifi"
AP_CONNECTION = "Hotspot-local" AP_CONNECTION = "Hotspot-local"
# 2026-08-12 swap: the fast TP-Link T3U (wlan1) hosts the in-car AP so phones # 2026-08-14 (FEAT-14): dual T3U — wlan1 (T3U #1) hosts the in-car AP so phones
# get 5GHz AC throughput; the weak onboard BCM43438 (wlan0) carries the # get 5GHz AC throughput; a second T3U (wlan2) carries the internet uplink
# internet uplink (home-wifi / phone-hotspot), which is background traffic. # (home-wifi / phone-hotspot); the onboard BCM43438 (wlan0) is only a fallback.
AP_INTERFACE = "wlan1" AP_INTERFACE = "wlan1"
PRIMARY_INTERFACE = "wlan0" # onboard — internet uplink PRIMARY_INTERFACE = "wlan2" # T3U #2 — internet uplink
FALLBACK_INTERFACE = "wlan1" # T3U — uplink fallback if onboard is absent FALLBACK_INTERFACE = "wlan0" # onboard BCM43438 — fallback if T3U #2 is absent
STATUS_PATH = "/var/lib/yaris-obd/uplink-status.json" STATUS_PATH = "/var/lib/yaris-obd/uplink-status.json"
PHONE_KEYFILE = ( PHONE_KEYFILE = (
"/etc/NetworkManager/system-connections/" "/etc/NetworkManager/system-connections/"
@@ -224,7 +224,7 @@ def _ap_client_count():
def check_uplink(): def check_uplink():
"""Manage internet uplink on wlan0 only. wlan1 AP is never touched.""" """Manage internet uplink on the uplink interface only. wlan1 AP is never touched."""
current = active_connection() current = active_connection()
phone_configured = connection_exists(PHONE_CONNECTION) phone_configured = connection_exists(PHONE_CONNECTION)
@@ -275,7 +275,7 @@ def check_uplink():
return True return True
return _connect_home_or_idle() return _connect_home_or_idle()
# Phone not configured. Home wifi is the only uplink on wlan1. # Phone not configured. Home wifi is the only uplink.
if current == HOME_CONNECTION: if current == HOME_CONNECTION:
write_status("home_connected_no_phone") write_status("home_connected_no_phone")
return True return True
+4 -4
View File
@@ -9,10 +9,10 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
INSTALLED_HELPER = "/usr/local/bin/yaris-uplink" INSTALLED_HELPER = "/usr/local/bin/yaris-uplink"
REPO_HELPER = os.path.join(BASE_DIR, "deploy", "yaris_uplink.py") REPO_HELPER = os.path.join(BASE_DIR, "deploy", "yaris_uplink.py")
PHONE_CONNECTION = "phone-hotspot" PHONE_CONNECTION = "phone-hotspot"
# 2026-08-12 swap: onboard (wlan0) carries the internet uplink; the fast # 2026-08-14 (FEAT-14): dual T3U — wlan1 (T3U #1) hosts the in-car AP; the
# T3U (wlan1) hosts the in-car AP. # second T3U (wlan2) carries the internet uplink; onboard wlan0 is a fallback.
PRIMARY_INTERFACE = "wlan0" PRIMARY_INTERFACE = "wlan2"
FALLBACK_INTERFACE = "wlan1" FALLBACK_INTERFACE = "wlan0"
AP_INTERFACE = "wlan1" AP_INTERFACE = "wlan1"
STATUS_PATH = "/var/lib/yaris-obd/uplink-status.json" STATUS_PATH = "/var/lib/yaris-obd/uplink-status.json"
+4 -4
View File
@@ -37,8 +37,8 @@ Dashboard — mobile (390 px) and desktop (1280 px):
can be cleared straight from the DTC page. can be cleared straight from the DTC page.
- **Real GPS tracking** — Waveshare SIM868 GNSS HAT with GPIO PWRKEY auto-power-on. Live position - **Real GPS tracking** — Waveshare SIM868 GNSS HAT with GPIO PWRKEY auto-power-on. Live position
plotted on an offline Leaflet map (pre-cached tiles, no internet needed). plotted on an offline Leaflet map (pre-cached tiles, no internet needed).
- **WiFi hotspot** — fast 5GHz in-car AP on the T3U (wlan1), internet uplink (home WiFi + - **WiFi hotspot** — fast 5GHz in-car AP on a T3U (wlan1), internet uplink (home WiFi +
phone hotspot) on the onboard radio (wlan0). SSID/password configurable from the Settings page. phone hotspot) on a second T3U (wlan2); the onboard radio is a fallback. SSID/password configurable from the Settings page.
- **Dashcam workflow** — auto-detects any USB dashcam, mounts read-only, browses files before - **Dashcam workflow** — auto-detects any USB dashcam, mounts read-only, browses files before
importing. Import one, selected, or all files. Delete individual or all files directly from the importing. Import one, selected, or all files. Delete individual or all files directly from the
dashcam. Verified against real 28GB/87-file hardware. dashcam. Verified against real 28GB/87-file hardware.
@@ -79,8 +79,8 @@ Dashboard — mobile (390 px) and desktop (1280 px):
| Raspberry Pi 3 Model B Rev 1.2 | Web UI host, logger, uploader, and WiFi hotspot | | Raspberry Pi 3 Model B Rev 1.2 | Web UI host, logger, uploader, and WiFi hotspot |
| Waveshare SIM868 HAT | GNSS (GPS) + optional GSM/GPRS uplink, unused Bluetooth | | Waveshare SIM868 HAT | GNSS (GPS) + optional GSM/GPRS uplink, unused Bluetooth |
| USB OBD-II adapter | PL2303-based, ELM327-compatible (originally Bluetooth, switched to USB) | | USB OBD-II adapter | PL2303-based, ELM327-compatible (originally Bluetooth, switched to USB) |
| TP-Link Archer T3U (RTL8812BU) | USB WiFi dongle — AC1300 dual-band. Hosts the 5GHz in-car AP (wlan1) since 2026-08-12 | | 2× TP-Link Archer T3U (RTL8812BU) | USB WiFi dongles — AC1300 dual-band. wlan1 hosts the 5GHz in-car AP; wlan2 carries the internet uplink since 2026-08-14 |
| Onboard BCM43438 (wlan0) | Internet uplink (home WiFi + phone hotspot) since 2026-08-12 | | Onboard BCM43438 (wlan0) | Fallback only — was the internet uplink until 2026-08-14 |
| USB dashcam | Any brand — detected as generic USB mass storage | | USB dashcam | Any brand — detected as generic USB mass storage |
| 2013 Toyota Yaris | The patient | | 2013 Toyota Yaris | The patient |