feat(pi): add a Wi-Fi hotspot mode for routerless direct streaming
scripts/pi-hotspot.sh turns the receiver into a WPA2 access point via NetworkManager (ipv4 shared mode gives the Pi built-in DHCP/NAT at 10.42.0.1), so a sender connects directly with no router in between — which also sidesteps LAN quirks like unreachable 6to4 addresses, since the direct link is plain private IPv4. The passphrase is generated on first use and stored root-only in /etc/screencast-hotspot.conf; on|off|status subcommands manage it, and off restores normal client Wi-Fi. The hotspot owns wlan0 while active (documented). No receiver changes were needed: it already announces on every interface. nmcli property syntax validated against NetworkManager 1.58 with a disposable never-activated profile; actual AP bring-up can only be validated on the Pi.
This commit is contained in:
@@ -65,6 +65,12 @@ loopback; current phase is Phase 7.
|
|||||||
before the fix). Cross-compiling on the dev machine was considered and
|
before the fix). Cross-compiling on the dev machine was considered and
|
||||||
dropped — the on-Pi build works and the toolchain/container effort was
|
dropped — the on-Pi build works and the toolchain/container effort was
|
||||||
not needed.
|
not needed.
|
||||||
|
- **Pi Wi-Fi hotspot** (`scripts/pi-hotspot.sh on|off|status`): NetworkManager
|
||||||
|
AP mode (WPA2, ipv4 shared → built-in DHCP/NAT, Pi at 10.42.0.1). Takes
|
||||||
|
over wlan0 while active; generated PSK stored in /etc/screencast-hotspot.conf.
|
||||||
|
No application changes needed — the receiver already announces on all
|
||||||
|
interfaces. nmcli property syntax validated against NM 1.58 with a
|
||||||
|
disposable profile; AP bring-up itself can only be validated on the Pi.
|
||||||
- **REAL-HARDWARE VALIDATION (desktop → Pi Zero 2 W over Wi-Fi)**: the full
|
- **REAL-HARDWARE VALIDATION (desktop → Pi Zero 2 W over Wi-Fi)**: the full
|
||||||
chain works on two machines: mDNS discovery → signaling negotiation →
|
chain works on two machines: mDNS discovery → signaling negotiation →
|
||||||
RTP over Wi-Fi → software H.264 decode → fullscreen KMSDRM letterboxed
|
RTP over Wi-Fi → software H.264 decode → fullscreen KMSDRM letterboxed
|
||||||
|
|||||||
@@ -90,6 +90,25 @@ Verified behavior: `systemctl stop` results in a clean exit (success,
|
|||||||
no restart) and the mDNS announcement is withdrawn — senders stop finding
|
no restart) and the mDNS announcement is withdrawn — senders stop finding
|
||||||
the receiver immediately.
|
the receiver immediately.
|
||||||
|
|
||||||
|
## Direct link: receiver as a Wi-Fi hotspot
|
||||||
|
|
||||||
|
The receiver can run as a Wi-Fi access point, so a sender connects to the
|
||||||
|
Pi directly with no router in between (on such a direct link the Pi is
|
||||||
|
plain private IPv4 — no 6to4 or router quirks):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo scripts/pi-hotspot.sh on # prints SSID + generated password
|
||||||
|
sudo scripts/pi-hotspot.sh status # shows the saved credentials
|
||||||
|
sudo scripts/pi-hotspot.sh off # back to normal router Wi-Fi
|
||||||
|
```
|
||||||
|
|
||||||
|
While the hotspot is active it owns the Wi-Fi interface: the Pi leaves any
|
||||||
|
router network, and the Pi is reachable at `10.42.0.1` (SSH included). On
|
||||||
|
the sender, join the hotspot's Wi-Fi and run `screencast --send` —
|
||||||
|
discovery and streaming then flow over the direct link. The passphrase is
|
||||||
|
generated on first use and stored in `/etc/screencast-hotspot.conf` (root
|
||||||
|
only). Override with `--ssid`, `--psk`, or `--band a` (5 GHz).
|
||||||
|
|
||||||
## Headless receiver (no window manager)
|
## Headless receiver (no window manager)
|
||||||
|
|
||||||
The receiver does not need X11, Wayland, or any window manager: SDL3's
|
The receiver does not need X11, Wayland, or any window manager: SDL3's
|
||||||
|
|||||||
@@ -207,4 +207,6 @@ Notes for small boards (e.g. Pi Zero 2 W):
|
|||||||
- On a desktop session instead, edit the installed service file and
|
- On a desktop session instead, edit the installed service file and
|
||||||
uncomment the Environment lines for XDG_RUNTIME_DIR/WAYLAND_DISPLAY.
|
uncomment the Environment lines for XDG_RUNTIME_DIR/WAYLAND_DISPLAY.
|
||||||
- avahi-daemon must stay enabled; it is what senders discover.
|
- avahi-daemon must stay enabled; it is what senders discover.
|
||||||
|
- For a routerless direct link: scripts/pi-hotspot.sh on turns the Pi
|
||||||
|
into a Wi-Fi access point (see docs/RUNBOOK.md).
|
||||||
EOF
|
EOF
|
||||||
Executable
+126
@@ -0,0 +1,126 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# pi-hotspot.sh — turn the receiver into a Wi-Fi access point so a sender
|
||||||
|
# can connect directly, without any router in between.
|
||||||
|
#
|
||||||
|
# Usage (on the Pi):
|
||||||
|
#
|
||||||
|
# sudo scripts/pi-hotspot.sh on [--ssid NAME] [--psk KEY] [--band bg|a]
|
||||||
|
# sudo scripts/pi-hotspot.sh off
|
||||||
|
# sudo scripts/pi-hotspot.sh status
|
||||||
|
#
|
||||||
|
# The hotspot takes over the Wi-Fi interface: the Pi leaves your router's
|
||||||
|
# network while it is active. Connect to the SSID from the sending machine;
|
||||||
|
# the Pi is reachable at 10.42.0.1 (SSH, screencast) and the sender gets an
|
||||||
|
# address via the built-in DHCP. Discovery and streaming then run over the
|
||||||
|
# direct link.
|
||||||
|
#
|
||||||
|
# The passphrase is stored in /etc/screencast-hotspot.conf (root only) so
|
||||||
|
# `status` can show it again.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
CONNECTION_NAME="screencast-hotspot"
|
||||||
|
INTERFACE="${SC_HOTSPOT_INTERFACE:-wlan0}"
|
||||||
|
CONFIG_FILE="/etc/screencast-hotspot.conf"
|
||||||
|
|
||||||
|
log() { printf '\n=== %s\n' "$*"; }
|
||||||
|
die() { printf 'pi-hotspot: error: %s\n' "$*" >&2; exit 1; }
|
||||||
|
|
||||||
|
require_root() {
|
||||||
|
[ "$(id -u)" = 0 ] || die "run with sudo"
|
||||||
|
}
|
||||||
|
|
||||||
|
random_passphrase() {
|
||||||
|
if command -v openssl >/dev/null 2>&1; then
|
||||||
|
openssl rand -base64 12 | tr -d '=+/' | head -c 16
|
||||||
|
else
|
||||||
|
tr -dc 'A-Za-z0-9' </dev/urandom | head -c 16
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_on() {
|
||||||
|
local ssid="screencast" psk="" band="bg"
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--ssid) ssid="$2"; shift 2 ;;
|
||||||
|
--psk) psk="$2"; shift 2 ;;
|
||||||
|
--band) band="$2"; shift 2 ;;
|
||||||
|
*) die "unknown option: $1" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
command -v nmcli >/dev/null 2>&1 || die "NetworkManager (nmcli) not found"
|
||||||
|
systemctl is-active --quiet NetworkManager || die "NetworkManager is not running"
|
||||||
|
ip link show "$INTERFACE" >/dev/null 2>&1 || die "interface ${INTERFACE} does not exist"
|
||||||
|
|
||||||
|
if [ -z "$psk" ]; then
|
||||||
|
psk="$(random_passphrase)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "warning: this takes over ${INTERFACE}; the Pi leaves any router Wi-Fi while the hotspot is active"
|
||||||
|
log "creating hotspot '${ssid}' (WPA2, band ${band})"
|
||||||
|
|
||||||
|
apt-get install -y --no-install-recommends network-manager dnsmasq-base >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
nmcli connection delete "$CONNECTION_NAME" >/dev/null 2>&1 || true
|
||||||
|
nmcli connection add \
|
||||||
|
type wifi \
|
||||||
|
ifname "$INTERFACE" \
|
||||||
|
con-name "$CONNECTION_NAME" \
|
||||||
|
autoconnect yes \
|
||||||
|
connection.autoconnect-priority 100 \
|
||||||
|
802-11-wireless.ssid "$ssid" \
|
||||||
|
802-11-wireless.mode ap \
|
||||||
|
802-11-wireless.band "$band" \
|
||||||
|
802-11-wireless-security.key-mgmt wpa-psk \
|
||||||
|
802-11-wireless-security.psk "$psk" \
|
||||||
|
ipv4.method shared \
|
||||||
|
ipv6.method disabled >/dev/null
|
||||||
|
|
||||||
|
nmcli connection up "$CONNECTION_NAME" >/dev/null
|
||||||
|
|
||||||
|
umask 077
|
||||||
|
printf 'ssid=%s\npsk=%s\n' "$ssid" "$psk" >"$CONFIG_FILE"
|
||||||
|
|
||||||
|
log "hotspot is up"
|
||||||
|
printf ' SSID: %s\n' "$ssid"
|
||||||
|
printf ' Password: %s\n' "$psk"
|
||||||
|
printf ' Pi address: 10.42.0.1 (also announced via mDNS)\n'
|
||||||
|
printf '\nOn the sending machine: connect to the Wi-Fi above, then\n'
|
||||||
|
printf ' screencast --send\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_off() {
|
||||||
|
log "removing the hotspot (Wi-Fi returns to normal client mode)"
|
||||||
|
nmcli connection delete "$CONNECTION_NAME" >/dev/null 2>&1 || {
|
||||||
|
log "no hotspot configured"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
rm -f "$CONFIG_FILE"
|
||||||
|
log "done; the Pi will reconnect to any known router Wi-Fi"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_status() {
|
||||||
|
if [ -f "$CONFIG_FILE" ]; then
|
||||||
|
log "hotspot configuration (${CONFIG_FILE})"
|
||||||
|
grep -E '^(ssid|psk)=' "$CONFIG_FILE" | sed -e 's/^/ /'
|
||||||
|
else
|
||||||
|
log "no hotspot configured (run: sudo scripts/pi-hotspot.sh on)"
|
||||||
|
fi
|
||||||
|
nmcli --get-values NAME,TYPE,DEVICE connection show --active 2>/dev/null | grep wifi | sed 's/^/ /' || true
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
on) require_root; shift; cmd_on "$@" ;;
|
||||||
|
off) require_root; shift; cmd_off "$@" ;;
|
||||||
|
status) cmd_status ;;
|
||||||
|
*)
|
||||||
|
cat <<'EOF'
|
||||||
|
usage: sudo scripts/pi-hotspot.sh on [--ssid NAME] [--psk KEY] [--band bg|a]
|
||||||
|
sudo scripts/pi-hotspot.sh off
|
||||||
|
sudo scripts/pi-hotspot.sh status
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Reference in New Issue
Block a user