A hardware-validated camera system that turns a Raspberry Pi 5 and an IMX708 NoIR autofocus camera into a secure LAN or offline wireless camera, controlled from a native macOS application.
The Raspberry Pi runs a loopback-only FastAPI/Picamera2 service. The macOS client reaches it through an SSH tunnel, provides a live MJPEG preview, and controls exposure, focus, digital zoom, still capture, and recording without exposing the camera API directly to the network.
Project status: working prototype validated on real Raspberry Pi 5 and IMX708 NoIR hardware. The Objective-C/AppKit client is the primary macOS app; the SwiftUI implementation is retained as an optional reference.
- Native macOS AppKit client that builds with Command Line Tools; no recent full Xcode installation is required.
- SSH-tunneled API: the Pi service listens on
127.0.0.1:8765by default. - 1280×720 MJPEG live preview and live camera metadata.
- 4608×2592 JPEG still capture.
- 1920×1080, 30 fps H.264/MP4 recording on the Pi.
- Continuous, single-shot, and manual autofocus controls.
- Live manual exposure and Camera Module 3 lens-position sliders.
- 1×–4× center-crop digital zoom for preview, photos, and recordings.
- Keychain storage for optional API tokens.
- Offline 5 GHz fallback hotspot support for field use without a router.
- Fake-camera backend and automated API tests for development without hardware.
IR Camera Controller.app
│
├── SSH: local 127.0.0.1:18765 → Pi 127.0.0.1:8765
│
└── HTTP API / MJPEG stream
│
FastAPI + Picamera2
│
Raspberry Pi 5 + IMX708 NoIR
The Pi owns the camera lifecycle as a single systemd service. Network loss does not interrupt an active Pi-side recording; finalized files can be downloaded after reconnecting.
- Raspberry Pi 5
- IMX708 autofocus NoIR camera (for example, Raspberry Pi Camera Module 3 NoIR)
- Raspberry Pi OS Lite 64-bit with SSH enabled
- Reliable Pi 5 power supply and active or adequate passive cooling
- microSD card with at least 16 GB; more capacity is recommended for recordings
The tested device uses Raspberry Pi OS Lite 64-bit (Debian 13 Trixie), a 128 GB card, and an IMX708 sensor at 4608×2592.
- macOS 12 or later
- Command Line Tools (
xcode-select --install) to rebuild the AppKit client - An SSH private key authorized for the Raspberry Pi account
Use Raspberry Pi Imager to install Raspberry Pi OS Lite 64-bit. In Imager's advanced settings, configure a hostname, create a user, enable SSH, and add the Mac's public SSH key.
Deploy the service from the repository root:
export PI_HOST=ir-camera.local
export PI_USER=<pi-user>
rsync -az pi/ "${PI_USER}@${PI_HOST}:/tmp/ir-camera-deploy/"
ssh "${PI_USER}@${PI_HOST}" 'sudo /tmp/ir-camera-deploy/install.sh'Verify the service:
ssh "${PI_USER}@${PI_HOST}" 'systemctl status ir-camera --no-pager'./macos-objc/build_app.shBuild products are created locally under macos-objc/dist/ and are excluded
from Git. Publish signed release binaries through GitHub Releases rather than
committing the .app bundle.
Open IR Camera Controller.app and enter:
- Host:
ir-camera.local - SSH user: the user created in Raspberry Pi Imager
- SSH key: for example
~/.ssh/id_ed25519 - API token: leave empty while the service remains loopback-only
Select Connect, then Start Live.
For use without a router, configure the Pi's built-in Wi-Fi as a low-priority fallback access point. Run this over Ethernet because activating it changes the Wi-Fi interface mode:
ssh "${PI_USER}@${PI_HOST}"
cd /tmp/ir-camera-deploy
sudo ./setup_offline_hotspot.shThe script securely prompts for a WPA2 password and creates:
- SSID:
IR-Camera-Directby default - Pi address:
10.42.0.1 - 5 GHz AP on channel 36
- autoconnect priority
-100
Normal saved Wi-Fi profiles with a higher priority remain preferred. When no
known network is available, connect the Mac to IR-Camera-Direct, then use
ir-camera.local or 10.42.0.1 in the app. The password is never stored in
this repository.
Create a tunnel:
ssh -N -L 18765:127.0.0.1:8765 "${PI_USER}@${PI_HOST}"Then, in another terminal:
curl http://127.0.0.1:18765/healthOpenAPI documentation is available at http://127.0.0.1:18765/docs while the
tunnel is active. See the API contract for endpoint details.
- The camera API binds to loopback by default and is not directly reachable from the LAN or hotspot.
- The Mac accesses the API only through an authenticated SSH tunnel.
- Binding the API to a non-loopback address requires a bearer token.
- Optional API tokens are stored in macOS Keychain.
- SSH private keys, Wi-Fi passwords, API tokens, and media are excluded from Git.
- Media uses opaque IDs; API clients cannot provide arbitrary filesystem paths.
The real-hardware acceptance run covers autofocus, manual controls, full-size JPEG capture, MJPEG streaming, 1080p30 recording, SSH tunneling, media cleanup, service recovery, offline hotspot startup, and a 108-second recording stress test. See the acceptance checklist and hardware validation notes.
Run the hardware-independent API tests with:
PYTHONPATH=pi/app python3 -m pytest -q pi/tests├── pi/ FastAPI/Picamera2 service, systemd unit, tests and tools
├── macos-objc/ Primary native AppKit client and build script
├── macos/ Optional SwiftUI/Xcode implementation
├── hardware/ OpenPiCase modification, printable STL and Blender tools
├── docs/ Architecture, setup, API, troubleshooting and acceptance
└── artifacts/ Selected real-hardware validation outputs
Generated apps, caches, Blender backups, third-party reference CAD files, and large validation videos are intentionally ignored. Use GitHub Releases or Git LFS if those binaries need to be distributed.
- Architecture
- Installation and build
- API contract
- Troubleshooting
- Acceptance checklist
- OpenPiCase hardware modification
The project source is released under the MIT License. Third-party reference CAD assets are excluded from the repository and remain subject to their respective owners' terms.