The Headless Pi 5 Sensor Node: Parts and Pinout
Running a Raspberry Pi headless (without a monitor or keyboard) is the standard for embedded IoT deployments. When you need to SSH into a Raspberry Pi from a Mac to manage a remote sensor node, you are bypassing the desktop environment entirely to save RAM and CPU cycles. This guide walks through deploying a headless environmental monitor using the latest hardware, establishing a secure SSH link from macOS, and debugging the inevitable network hiccups.
Hardware Parts List
- Compute: Raspberry Pi 5 (4GB) - Do not use the 8GB for simple sensor polling; 4GB is sufficient and runs cooler.
- Power: Official Raspberry Pi 27W USB-C PD Power Supply. Using a standard 15W phone charger will trigger low-voltage warnings on the Pi 5 and throttle the CPU.
- Sensor: Adafruit BME280 I2C/SPI Temperature/Humidity/Pressure Sensor (Product ID: 2652).
- Wiring: 4x female-to-female jumper wires or a 20-pin GPIO ribbon cable.
- Storage: 32GB SanDisk Extreme microSD (A2 rating for faster OS boot times).
Pi 5 to BME280 Pin Mapping Table
The BME280 communicates via I2C. On the Raspberry Pi 5, the default I2C bus (i2c-1) is mapped to the following physical pins on the 40-pin header.
| BME280 Pin | Pi 5 GPIO (BCM) | Pi 5 Physical Pin | Function |
|---|---|---|---|
| VIN | N/A | 1 or 17 | 3.3V Power |
| GND | N/A | 6, 9, 14, or 20 | Ground |
| SDA | GPIO 2 | 3 | I2C Data |
| SCL | GPIO 3 | 5 | I2C Clock |
Prepping the Mac and Pi for Passwordless SSH
Before you can SSH into the Raspberry Pi from your Mac, you need to configure the OS image to allow headless access right out of the gate. The days of blindly plugging in a monitor to run sudo raspi-config are over.
Step 1: Flash with OS Customization
Use the Raspberry Pi Imager on your Mac. Select the Pi 5, choose Raspberry Pi OS Lite (64-bit), and click the gear icon (OS Customization). You must check 'Enable SSH' and select 'Use password authentication' (or inject your Mac's public key directly here). Set your hostname to env-node and configure your local WiFi SSID and password.
Step 2: Generate and Copy SSH Keys on Mac
Password authentication is a security risk for exposed nodes. Generate an Ed25519 key pair on your Mac terminal and push it to the Pi.
# Generate the key on your Mac (press Enter to accept defaults, leave passphrase empty for automated cron jobs)
ssh-keygen -t ed25519 -C "mac-to-pi-node"
# Copy the key to the Pi using mDNS (.local)
ssh-copy-id pi@env-node.local
Step 3: Connection Decision Tree
How you address the Pi on the network depends on your router's mDNS support and your physical setup. Use this decision path to pick your connection method.
| Scenario | Method | Command | Verdict |
|---|---|---|---|
| Standard Home Network (Mac & Pi on same WiFi/Ethernet) | mDNS Hostname | ssh pi@env-node.local | Default Pick. Most reliable for local LAN. No IP tracking needed. |
| Router blocks mDNS / Enterprise VLAN | Static IP via DHCP Reservation | ssh pi@192.168.1.50 | Use when .local fails. Reserve IP in router admin panel. |
| No WiFi/Ethernet available (Field deployment) | USB-C Direct Ethernet | ssh pi@10.55.0.1 | Pi 5 supports USB-C gadget mode, but requires config.txt tweaks. Use only as fallback. |
Deploying the BME280 Python Script via SSH
Once logged in via ssh pi@env-node.local, you need to install the I2C tools and the Adafruit CircuitPython libraries. The Pi 5 Bookworm release uses PEP 668, meaning you cannot use pip install globally without breaking system packages. You must use a virtual environment.
# Enable I2C interface via the new raspi-config non-interactive flag
sudo raspi-config nonint do_i2c 0
# Create and activate a virtual environment
python3 -m venv ~/sensor_env
source ~/sensor_env/bin/pip
pip install adafruit-circuitpython-bme280
Compilable Python Sensor Script
Save the following code as read_bme.py. This script includes explicit pin definitions via the board module and robust error handling for I2C bus lockups, which are common on long wire runs.
import time
import sys
import board
import busio
import adafruit_bme280
# Target: Raspberry Pi 5 - Default I2C (GPIO3=SCL, GPIO2=SDA)
# If using an alternate I2C bus, define explicitly: busio.I2C(board.D5, board.D6)
i2c = board.I2C()
def init_sensor():
try:
# BME280 default I2C address is 0x77. Adafruit breakout is 0x77.
sensor = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=0x77)
sensor.sea_level_pressure = 1013.25
return sensor
except ValueError as e:
print(f'FATAL: Sensor not found on I2C bus. Check wiring. Error: {e}', file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f'FATAL: I2C Bus locked or permission denied. Error: {e}', file=sys.stderr)
sys.exit(1)
def main():
sensor = init_sensor()
print('BME280 Initialized. Polling every 10 seconds...')
while True:
try:
temp_c = sensor.temperature
humidity = sensor.relative_humidity
pressure = sensor.pressure
# Output formatted for easy parsing by bash scripts or MQTT
print(f'DATA | Temp: {temp_c:.2f}C | Hum: {humidity:.1f}% | Press: {pressure:.2f}hPa')
sys.stdout.flush()
time.sleep(10)
except OSError as e:
# Handle transient I2C read errors without crashing the daemon
print(f'WARN: I2C Read Error: {e}. Retrying in 5s...', file=sys.stderr)
time.sleep(5)
sensor = init_sensor() # Re-initialize on bus failure
except KeyboardInterrupt:
print('\nShutting down gracefully.')
sys.exit(0)
if __name__ == '__main__':
main()
Troubleshooting: Connection Refused and mDNS Failures
When you try to SSH into a Raspberry Pi from a Mac, network discovery is usually the point of failure. Here is how to diagnose the two most common exact error strings.
Error 1: The mDNS Timeout
Exact Error String: ssh: Could not resolve hostname env-node.local: nodename nor servname provided, or not known
This means your Mac's Bonjour (mDNS) service cannot find the Pi's broadcasted hostname.
- Cause A (Most Likely): The Pi is connected to a 5GHz-only WiFi network, and your Mac is on the 2.4GHz band, with AP isolation enabled on the router.
- Cause B: The Pi hasn't finished booting. The
avahi-daemon(which handles mDNS on Linux) takes about 15-20 seconds to start after the Pi 5 boots. - Fix: Ping the IP address directly. Log into your router's admin panel, find the Pi's assigned IP in the DHCP client list, and use
ssh pi@192.168.x.x.
Error 2: The Port Block
Exact Error String: ssh: connect to host 192.168.1.42 port 22: Connection refused
This is a TCP-level rejection. Your Mac found the IP, but the Pi slammed the door in its face.
- Is the
sshfile present? If you didn't use the Raspberry Pi Imager's OS customization, you must manually create an empty file namedssh(no extension) in the root of the FAT32 boot partition of the SD card before booting. - Is the Pi actually on the network? Check your router's DHCP lease table. If the Pi isn't listed, it failed to connect to WiFi (check your
wpa_supplicant.confor Imager WiFi password for typos). - Is your Mac on the same VLAN/Subnet? If your Mac is on a guest network or a segmented IoT VLAN that blocks local peer-to-peer traffic, the connection will be refused or time out.
Extending and Simplifying the Build
A headless node is only as good as its data pipeline. Once your SSH connection is stable and the Python script is running via systemd, you have two paths forward depending on your project scope.
How to Extend: Add MQTT Telemetry
Polling a script via SSH is fine for debugging, but terrible for production. Extend the build by installing paho-mqtt in your virtual environment. Modify the read_bme.py script to publish the DATA string to an MQTT broker (like Mosquitto running on a Home Assistant server). This allows you to completely sever the SSH connection and let the Pi push data asynchronously. Add a 10k pull-up resistor to the SDA line if your I2C wire run exceeds 30cm to prevent MQTT publish drops caused by sensor read timeouts.
How to Simplify: Ditch Python for Rust/C
If you are strictly logging to a local CSV and want to eliminate the 40MB RAM overhead of the Python virtual environment, simplify the build by compiling a lightweight C binary using the WiringPi or pigpio libraries. However, for 95% of hobbyist and prosumer IoT deployments, the Python + systemd route offers the best balance of development speed and runtime stability.
Final Recommendation: Stick to the Raspberry Pi Imager OS Customization for initial setup, use Ed25519 SSH keys for authentication, and rely on mDNS (.local) for your daily terminal access. It is the most frictionless workflow for Mac-to-Pi embedded development available today.






