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
|
||||
dropped — the on-Pi build works and the toolchain/container effort was
|
||||
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
|
||||
chain works on two machines: mDNS discovery → signaling negotiation →
|
||||
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
|
||||
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)
|
||||
|
||||
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
|
||||
uncomment the Environment lines for XDG_RUNTIME_DIR/WAYLAND_DISPLAY.
|
||||
- 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
|
||||
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