New Door Access System created in 2025
Python 3 Raspberry Pi Zero RFID door access control system with cloud-based badge management, health monitoring, and robust logging.
Code was made by Adam who knows VERY little of python and utilized ChatGPT to get it done. Please feel free to suggest more efficient changes
Hardware (see wiring diagram):
- Raspberry Pi Zero W
- PN532 RFID Reader
- 12v Relay
- 12v to 5v buck converter
- 12v door latch
- 12v Power Suply
- 2 Generic Buttons
- 3D Printed case (will be added soon)
Main Functions:
- start.py contains all of the code and runs as a service in order to make sure it is running all of the time. The theory is if it crashes, the service can restart automatically. Also the service will start on reboot without having to wait on something like a cron job to restart it.
- When RFID is scanned the pi reaches out to a Sheets file on FamiLAB's Google Drive that contains all allowed IDs. If the ID is found it triggers the relay to unlock the door for 5 seconds and posts in another Google Sheets document. It posts the ID and "Granted" Or "Denied". This is used for logging and troubleshooting.
- Two buttons are also available. One is to unlock the door for 1 hour and the other is to override the unlock and set the door back to locked.
Credentials:
- The google doc is shared with a service account and credentials are saved to a local file. See https://docs.gspread.org/en/latest/oauth2.html for more information about how gspread works.
- Features
- Hardware Requirements
- GPIO Pin Configuration
- Installation
- Running the Application
- Health Monitoring
- Logging System
- Systemd Watchdog
- Testing
- Continuous Integration
- Deployment (production)
- Additional Documentation
- Configuration Options
- Architecture
- Troubleshooting
- Security Considerations
- License
- Support
- NFC/RFID Badge Authentication: PN532-based badge reader with Google Sheets integration
- Physical Controls: Manual unlock (1-hour) and lock buttons
- Health Monitoring: Web-based health dashboard with real-time system status
- Admin Toggle Control: Authenticated
/admintoggle button with lock/unlock state icon and action label - Metrics Dashboard: Authenticated
/metricspage with charts, pagination, date filters, and monthly exports - Robust Logging: 7-day rotating local logs with Google Sheets failover
- Systemd Integration: Auto-restart on failure with watchdog heartbeat
- Thread-Safe: Concurrent button monitoring and RFID scanning
- Failover Support: Local CSV backup when Google Sheets unavailable
- Raspberry Pi Zero (or any Raspberry Pi with GPIO)
- PN532 NFC/RFID Reader (I2C)
- Relay module (for door latch control)
- 2x Push buttons (unlock/lock)
- Door latch/strike (controlled via relay)
Default pin assignments (configurable via config.py or environment variables):
- GPIO 17: Relay control (door latch)
- GPIO 27: Unlock button (1-hour unlock)
- GPIO 22: Lock button (manual lock override)
- I2C: PN532 NFC reader (SDA/SCL)
git clone <repository-url>
cd badge_scannerpython3 -m venv venv
source venv/bin/activate # On Linux/macOS
# or
venv\Scripts\activate # On WindowsFor older Raspberry Pi models (Pi Zero, Pi 3, etc.):
First upgrade pip to avoid dependency resolver issues:
pip install --upgrade pip setuptools wheelThen install the project dependencies:
pip install -r requirements.txtIf SSH connection times out during installation (common on slower Pi models), run installation in background:
nohup pip install -r requirements.txt > install.log 2>&1 &Monitor the installation progress:
tail -f install.logPress Ctrl+C to stop monitoring (installation continues in background)
- Create a Google Cloud Project
- Enable Google Sheets API and Google Drive API
- Create a Service Account and download credentials
- Save credentials as
creds.jsonin the project directory - Share your Google Sheets with the service account email
Create two Google Sheets:
- Badge List - Access Control: Contains authorized badge UIDs (column 1)
- Access Door Log: Logs all access attempts (timestamp, UID, status)
Edit config.py or set environment variables:
export DOOR_HEALTH_PORT=3667
export DOOR_HEALTH_USERNAME=admin
export DOOR_HEALTH_PASSWORD=changemeYou can run the application locally on Windows for development without GPIO/PN532 hardware. The start.py script will automatically fall back to lightweight stubs if the hardware libraries are not available.
Recommended (optional): install a GPIO emulator package so code that imports RPi.GPIO still works.
Note: there is no single canonical emulator package on PyPI — names vary. The project includes local stubs (src_service/gpio_stub.py and src_service/pn532_stub.py) which are used automatically when real hardware packages are missing.
If you want to try an emulator, these are common candidates (may or may not exist on PyPI):
pip install fake-rpi # or
pip install fake_rpiIf you prefer the convenience script, use:
.\scripts\run_dev.ps1 -InstallThe script will install a Windows-friendly subset (requirements-windows.txt) and will also try installing common emulator packages; if none are available it will fall back to the included stubs.
Development helper scripts:
- Windows PowerShell:
.\scripts\run_dev.ps1 -Install(installs dependencies and runsstart.py) - Linux/macOS:
./scripts/run_dev.sh install(installs dependencies and runsstart.py)
# Activate virtual environment
source venv/bin/activate
# Run application
python3 start.py- Copy the service file:
sudo cp door-app.service /etc/systemd/system/- Edit service file paths:
sudo nano /etc/systemd/system/door-app.serviceUpdate WorkingDirectory and ExecStart paths to match your installation.
- Enable and start service:
sudo systemctl daemon-reload
sudo systemctl enable door-app.service
sudo systemctl start door-app.service- Check status:
sudo systemctl status door-app.service- View logs:
sudo journalctl -u door-app.service -fThe application provides a web-based health dashboard at:
http://<raspberry-pi-ip>:3667/health
Default credentials: admin / changeme (change in config!)
API Documentation (Swagger UI): An interactive API documentation (Swagger UI) is served at:
http://<raspberry-pi-ip>:3667/docs
The raw OpenAPI JSON spec is available at /openapi.json (e.g., http://<raspberry-pi-ip>:3667/openapi.json). Access to the API docs is protected by the same Basic Auth credentials as the health page.
- The health server can optionally serve HTTPS. Enable TLS with the
HEALTH_SERVER_TLSconfig option or environment variableDOOR_HEALTH_TLS=true. - When TLS is enabled and no certificate is provided, the server will attempt to generate a self-signed certificate (default path:
cert.pemin the project root). For production, prefer providing a trusted certificate or terminate TLS at a reverse proxy (e.g., nginx) that handles certificates. - If TLS initialization fails (missing dependency or cert generation/wrapping error), the server will fall back to HTTP and log the reason. The server logs the actual scheme (
httporhttps) and the port during startup.
/adminincludes:- Manual badge refresh
- Door lock/unlock toggle (
POST /api/toggle) - Link to
/metrics
/metricsincludes:- Graphs backed by
/api/metrics/* - Date-range filtering and pagination
- Monthly data export (
csv/json) - Browser-side chart export (
png/svg)
- Graphs backed by
- Door status (OPEN/CLOSED)
- Last local log entry timestamp
- Last successful Google Sheets log
- Last badge list download
- Application uptime
- PN532 reader status (last success/error)
- Google Sheets status (last error)
- Log file size and disk space
- Auto-refreshes every 30 seconds
- Location:
door_controller.log - Rotation: Daily, keeps 7 days
- Content: All door actions, badge scans, errors
- Best-effort: Never crashes app on failure
- Failover: Falls back to local-only logging
- Logged Events:
- Badge scans (granted/denied)
- Manual unlock/lock actions
- System errors
The application uses two watchdog-related files:
logs/door_controller_watchdog-YYYY-MM-DD.txt(dated): a daily rotating watchdog log file produced by the watchdog logger; retained perLOG_RETENTION_DAYS.logs/door_controller_watchdog_heartbeat.txt(no date): a single non-dated heartbeat file that records the last time the watchdog updated a timestamp. The watchdog writes the current timestamp to this file at the heartbeat interval and it can be monitored by systemd or other tools to detect liveness.
Configure systemd to monitor the (non-dated) heartbeat file:
[Service]
ExecStartPre=/bin/sh -c 'mkdir -p /tmp && echo "0" > /tmp/door_controller_watchdog.txt || true'
Restart=always
RestartSec=5Systemd will auto-restart the service if it crashes or hangs.
# Activate virtual environment
source venv/bin/activate
# Run all tests
python -m unittest discover -s tests -p "test_*.py" -vpython -m unittest tests.test_logging_utils -vpip install coverage
coverage run -m unittest discover -s tests
coverage report -m
coverage html # Generates htmlcov/index.htmlGitHub Actions automatically runs tests on push/PR to main/develop branches.
See .github/workflows/tests.yml for CI configuration.
Tests run on Python 3.9, 3.10, and 3.11.
This repository includes a deployment workflow that builds a ZIP artifact and deploys it to a self-hosted production agent.
- Optimizations — Performance and power optimizations for devices (Wi‑Fi power saving, etc.)
- Quick Reference — Short commands and common operations
- Data Schema — Google Sheets structure and expected formats (badge list and access log)
- Metrics — SQLite ingestion/query design and metrics API behavior
- API Docs (
/docs) — Interactive Swagger UI for exploring the HTTP API (OpenAPI JSON at/openapi.json)
Required repository secrets (set under Settings → Secrets):
CREDS_JSON— (optional) the full Google Service Account JSON content (will be written tocreds.jsonon the target host)DOOR_HEALTH_USERNAME— username for the health page (recommended to change fromadmin)DOOR_HEALTH_PASSWORD— password for the health page (set a strong value)DOOR_HEALTH_PORT— (optional) port for the health server (default 8080)DEPLOY_DIR— (optional) directory on the target host to deploy files (default:/opt/door)
Protection and approvals:
- The
deployjob targets theproductionenvironment. Configure environment protection rules in GitHub to require approvals or checks before the workflow can proceed.
Behavior of the deployment workflow:
- Job 1 (
build_package) creates a ZIP containingREADME.md, all*.mdfiles,*.servicefiles,version*.txt,main.py, thesrc_service/package, andrequirements.txt. - Job 2 (
deploy) runs on a self-hosted runner (an agent you own), downloads the ZIP, extracts it toDEPLOY_DIR(default/opt/door), writes thecreds.jsonfile ifCREDS_JSONis provided, creates a systemd drop-in to exportDOOR_CREDS_FILE,DOOR_HEALTH_USERNAME, andDOOR_HEALTH_PASSWORDinto the service environment, then restartsdoor-app.service.
Notes & recommended follow-ups:
- Secrets are never committed to the repository; they are provided to the workflow via GitHub Secrets.
- The deployment writes the service account JSON to
creds.jsonand setsDOOR_CREDS_FILEto point there. The service will read the config at startup. - You may prefer to manage secrets via a secret management system or encrypted files on the target host.
| Variable | Default | Description |
|---|---|---|
DOOR_RELAY_PIN |
17 | GPIO pin for relay |
DOOR_UNLOCK_PIN |
27 | GPIO pin for unlock button |
DOOR_LOCK_PIN |
22 | GPIO pin for lock button |
DOOR_UNLOCK_DURATION |
3600 | Unlock duration (seconds) |
DOOR_HEALTH_PORT |
3667 | Health server port |
DOOR_HEALTH_USERNAME |
admin | Health page username |
DOOR_HEALTH_PASSWORD |
changeme | Health page password |
DOOR_METRICS_DB_PATH |
logs/metrics | Base directory for monthly SQLite metrics DBs |
Create config.json to override defaults:
{
"RELAY_PIN": 17,
"UNLOCK_DURATION": 3600,
"HEALTH_SERVER_PORT": 3667,
"METRICS_DB_PATH": "logs/metrics"
}start.py: Main application entry pointconfig.py: Configuration managementlogging_utils.py: Logging system (local + Google Sheets)door_control.py: Door status tracking and GPIO controlhealth_server.py: HTTP health monitoring serverwatchdog.py: Systemd watchdog heartbeat
- Main thread: Initialization and thread management
- Button monitor thread: Polls unlock/lock buttons
- RFID monitor thread: Reads PN532 and authenticates badges
- Health server thread: HTTP server (daemon)
- Watchdog thread: Heartbeat updates (daemon)
# Check service status
sudo systemctl status door-app.service
# View detailed logs
sudo journalctl -u door-app.service -n 50If you encounter dependency resolver errors or cryptography package issues on older Raspberry Pi models (Python 3.9 or earlier):
# Upgrade pip, setuptools, and wheel first
pip install --upgrade pip setuptools wheel
# Clear pip cache
pip cache purge
# Then install requirements
pip install -r requirements.txtIf issues persist, try using the legacy resolver:
pip install --no-cache-dir --use-deprecated=legacy-resolver -r requirements.txtIf SSH connection times out during installation, run in background:
nohup pip install -r requirements.txt > install.log 2>&1 &
tail -f install.log# Add user to gpio group
sudo usermod -a -G gpio $USER
# Reboot required
sudo reboot# Check I2C devices
sudo i2cdetect -y 1
# Should show device at 0x24- Verify
creds.jsonis valid - Check service account has access to sheets
- Ensure internet connectivity
- Check local logs:
tail -f door_controller.log
- Check firewall:
sudo ufw allow 3667 - Verify port in config matches URL
- Check server started:
netstat -tuln | grep 3667
- Change default health page credentials immediately
- Use HTTPS reverse proxy (nginx) for production health page
- Restrict health page access via firewall rules
- Keep
creds.jsonsecure (never commit to git) - Regularly rotate Google service account keys
- Review access logs periodically
[Your License Here]
[Your Support Information Here]
- systemctl cat door-app.service
- systemctl --type=service --state=running