| |

Raspberry Pi Automatic Wi-Fi ↔ Hotspot Switching (NetworkManager)

Goal

Boot
  • manual flag on → hotspot
  • manual flag off:
    • known Wi-Fi available → Wi-Fi
    • otherwise → hotspot
Hotspot active
  • manual flag on → stay on hotspot
  • manual flag off:
    • known Wi-Fi appears → switch to Wi-Fi
Wi-Fi active
  • stay on Wi-Fi
  • if Wi-Fi disappears:
    • try known Wi-Fi again
    • if none available → hotspot

Design

There are two connection classes on wlan0:

  • one or more normal Wi-Fi client profiles
  • one hotspot profile called device-hotspot

Rules:

  1. Wi-Fi profiles have autoconnect enabled.
  2. Hotspot profile has autoconnect disabled.
  3. If Wi-Fi disconnects and hotspot was not manually requested, the dispatcher starts the hotspot.
  4. If hotspot is active and the user clears manual mode, Wi-Fi becomes normal again.
  5. A persistent flag file records whether hotspot is manual or automatic.

Step 1 — Install and enable NetworkManager

Install:

sudo apt update
sudo apt install -y network-manager

If dhcpcd is managing the interface on your Pi image, disable it so NetworkManager is the single authority:

sudo systemctl disable --now dhcpcd

Enable NetworkManager:

sudo systemctl enable --now NetworkManager

Step 2 — Create the hotspot profile

Create the hotspot once:

sudo nmcli dev wifi hotspot ifname wlan0 ssid <ssid> password <MyStrongPassword> autoconnect no

That creates a normal NetworkManager connection profile named ‘Hotspot’. Rename it if needed:

sudo nmcli connection modify Hotspot connection.id device-hotspot

Step 3 — Add known Wi-Fi networks and make them preferred

Add Wi-Fi networks normally:

sudo nmcli dev wifi rescan
sudo nmcli device wifi list
sudo nmcli dev wifi connect "MyWiFi" password "MyPassword"

For each normal Wi-Fi profile, keep autoconnect enabled and assign a positive priority:

sudo nmcli connection modify "MyWiFi" connection.autoconnect yes
sudo nmcli connection modify "MyWiFi" connection.autoconnect-priority 100

If you have multiple normal Wi-Fi profiles, give the more important one a higher number. NetworkManager prefers higher autoconnect-priority values when several profiles are candidates.

List profiles:

nmcli connection show

Step 4 — Create a network selection script

First create the persistent state directory:

sudo mkdir -p /var/lib/pi-network-mode

Now create the network management script:

sudo nano /usr/local/bin/wifi-logic.sh

Paste this code inside of it:

#!/bin/bash

# 1. LOCKING (Keep this to prevent the "Trigger Storm")
LOCKFILE="/tmp/wifi-logic.lock"
if [ -e ${LOCKFILE} ] && kill -0 $(cat ${LOCKFILE}) 2>/dev/null; then
    exit 0
fi
echo $$ > ${LOCKFILE}
trap 'rm -f ${LOCKFILE}' EXIT

# 2. LOGGING & CONFIG
FLAG="/var/lib/pi-network-mode/manual-hotspot"
HS_CON="device-hotspot"

log_msg() {
    logger -t WiFi-Logic "$1"
}

WLAN_STATE=$(nmcli -t -f DEVICE,STATE dev | grep "^wlan0:" | cut -d: -f2)
CURRENT_CON=$(nmcli -t -f NAME con show --active | head -n 1)

log_msg "State: [$WLAN_STATE] Active: [$CURRENT_CON]"

# 3. MANUAL FLAG
if [ -f "$FLAG" ]; then
    if [ "$CURRENT_CON" != "$HS_CON" ]; then
        log_msg "Manual Flag ON. Forcing Hotspot."
        nmcli con up "$HS_CON"
    fi
    exit 0
fi

# 4. SMART EXIT
if [[ "$WLAN_STATE" == "connected" ]] && [[ "$CURRENT_CON" != "$HS_CON" ]]; then
    exit 0
fi

# 5. SCANNING
# We use a forced rescan. If the radio is busy, this might fail, so we catch it.
VISIBLE_SSIDS=$(nmcli -t -f SSID dev wifi list --rescan yes 2>/dev/null | sed 's/\\//g')
SAVED_PROFILES=$(nmcli -t -f NAME,TYPE connection show | awk -F: '$2=="802-11-wireless" && $1!="device-hotspot" {print $1}')

MATCH_PROFILE=""
MATCH_SSID=""

while read -r PROFILE; do
    [ -z "$PROFILE" ] && continue
    SSID_OF_PROFILE=$(nmcli -g 802-11-wireless.ssid connection show "$PROFILE" 2>/dev/null)
    if echo "$VISIBLE_SSIDS" | grep -Fqx "$SSID_OF_PROFILE"; then
        MATCH_PROFILE="$PROFILE"
        MATCH_SSID="$SSID_OF_PROFILE"
        break
    fi
done <<< "$SAVED_PROFILES"

# 6. EXECUTION
if [ -z "$MATCH_PROFILE" ]; then
    # CASE: NO WIFI FOUND -> START HOTSPOT
    if [ "$CURRENT_CON" != "$HS_CON" ]; then
        log_msg "No Wi-Fi visible. Activating Hotspot."
        nmcli con down id "$CURRENT_CON" 2>/dev/null
        sleep 3
        nmcli con up "$HS_CON"
    fi
else
    # CASE: WIFI FOUND -> SWITCH BACK
    if [ "$CURRENT_CON" != "$MATCH_PROFILE" ]; then
        log_msg "Known Wi-Fi [$MATCH_SSID] detected. Performing Clean Swap..."

        # 1. Kill Hotspot
        nmcli con down "$HS_CON" 2>/dev/null

        # 2. HARD RESET wlan0 (Clears the "occupied" state)
        nmcli device disconnect wlan0 2>/dev/null

        # 3. Give the hardware a significant breather (Zero 2 W needs this)
        sleep 5

        # 4. Try to connect. We use the profile name here.
        if nmcli con up "$MATCH_PROFILE"; then
            log_msg "Successfully reconnected to [$MATCH_SSID]."
        else
            log_msg "Connection to [$MATCH_SSID] failed. Reverting to Hotspot."
            sleep 2
            nmcli con up "$HS_CON"
        fi
    fi
fi

And make it executable:

sudo chmod +x /usr/local/bin/wifi-logic.sh

Step 5 — Create system services

Network Event Trigger (Dispatcher)

This will run the script when network state changes, i.e. when the WiFi network disappears.

sudo nano /etc/NetworkManager/dispatcher.d/99-wifi-failover

Paste:

#!/bin/bash
INTERFACE=$1
if [ "$INTERFACE" = "wlan0" ]; then
# Trigger the brain on any wlan0 change
/usr/local/bin/wifi-logic.sh &
fi

Make it executable:

sudo chmod +x /etc/NetworkManager/dispatcher.d/99-wifi-failover

The Heartbeat (Polling)

This ensures the Pi scans for home Wi-Fi while the Hotspot is active.

sudo nano /etc/systemd/system/wifi-heartbeat.service

Paste:

[Unit]
Description=WiFi Logic Executor
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/wifi-logic.sh

Make it run every two minutes:

sudo nano /etc/systemd/system/wifi-heartbeat.timer

Paste:

[Unit]
Description=Run WiFi Logic every 2 minutes

[Timer]
OnBootSec=1min
OnUnitActiveSec=2min
Unit=wifi-heartbeat.service

[Install]
WantedBy=timers.target

Boot Initialization

Run the script on startup:

sudo nano /etc/systemd/system/wifi-init.service

Paste inside this file:

[Unit]
Description=Initialize WiFi Mode on Boot
After=NetworkManager.service
Wants=NetworkManager.service

[Service]
Type=oneshot
ExecStartPre=/bin/sleep 10
ExecStart=/usr/local/bin/wifi-logic.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

Activate systemd files

sudo systemctl daemon-reload
sudo systemctl enable --now wifi-heartbeat.timer
sudo systemctl enable wifi-init.service

How it works

Verification

To ensure your Raspberry Pi Zero 2 W is currently “listening” to all the triggers, you can run this combined command. It will check the Timer, the Boot Service, and the Dispatcher’s presence in one go.

echo "--- Timers ---"; systemctl list-timers wifi-heartbeat.timer; \
echo "--- Boot Service ---"; systemctl is-enabled wifi-init.service; \
echo "--- Dispatcher ---"; [ -x /etc/NetworkManager/dispatcher.d/99-wifi-failover ] && echo "Executable" || echo "NOT EXECUTABLE"

View script logs (Follow script’s “thoughts” in real-time):

journalctl -t WiFi-Logic -f

[Option -f is used to keep the log open, and see the results in real-time]

View network manager logs (See the last 50 lines of the system’s actual network manager. This is where you’ll see “Invalid Password” or “Association Failed” errors):

journalctl -u NetworkManager -n 50

Turning Hotsopt On and Off

To manually turn Hotspot on:

sudo touch /var/lib/pi-network-mode/manual-hotspot
sudo /usr/local/bin/wifi-logic.sh

To manually turn Hotspot off (and connect to WiFi if in range):

sudo rm /var/lib/pi-network-mode/manual-hotspot
sudo /usr/local/bin/wifi-logic.sh

Useful commands

  • Get the list of available networks:
sudo nmcli dev wifi rescan
sudo nmcli device wifi list
  • Active connections:
sudo nmcli connection show --active
  • Device state (See if wlan0 is connected, disconnected, or connecting):
sudo nmcli device status
  • Scan networks:
sudo nmcli device wifi list
  • If you want to see everything about one specific saved network (like your home Wi-Fi), run:
nmcli connection show "<connection name>"
  • This command provides a table showing the Profile Name, the SSID it looks for, and its Priority:
printf "%-24s %-24s %-6s %-5s\n" "NAME" "SSID" "AUTO" "PRIO"
printf "%-24s %-24s %-6s %-5s\n" "------------------------" "------------------------" "------" "-----"
nmcli -t -f NAME,TYPE connection show | awk -F: '$2=="802-11-wireless"{print $1}' | while read -r c; do
    ssid=$(nmcli -g 802-11-wireless.ssid connection show "$c")
    auto=$(nmcli -g connection.autoconnect connection show "$c")
    prio=$(nmcli -g connection.autoconnect-priority connection show "$c")
    printf "%-24s %-24s %-6s %-5s\n" "$c" "$ssid" "$auto" "$prio"
done

Behavior summary

ScenarioResult
Wi-Fi availableconnect normally
Wi-Fi unavailablehotspot starts
Wi-Fi returnshotspot stops
hotspot forced by userhotspot persists

Similar Posts