Source Code
A1 Mini Overhead Toolhead Camera#
This is intended to be run on a Raspberry Pi Zero 2W Raspberry Pi Camera Module 3 running RPi OS Lite (bookworm).
Note
Optionally, complete the following network setup steps, applicable to devices such as Raspberry Pis running RPi OS Lite.
Consider setting up Tailscale for secure remote access and management. Please refer to the Tailscale Setup Guide for detailed instructions on installation and configuration.
If you need to connect to a WPA2-Enterprise WiFi network (common in institutional environments), please refer to the Raspberry Pi WPA2-Enterprise WiFi Setup Guide first.
Architecture Overview#
The A1 Mini camera system uses a hybrid approach for image transfer:
MQTT - Used to send capture commands to the device and receive image URIs back
AWS S3 - Used to store the actual image files (avoids MQTT payload size limitations)
This architecture follows the recommendations from ac-microcourses data logging tutorial which suggests uploading files to cloud storage and storing URIs in your database rather than embedding large binary data.
AWS S3 Setup#
1. Create an AWS Account#
If you don’t have an AWS account, create one at aws.amazon.com. AWS offers a free tier that includes 5 GB of S3 storage for 12 months.
2. Create an S3 Bucket#
Follow the official AWS documentation to create an S3 bucket:
Recommended bucket configuration settings (based on Issue #159):
Key considerations:
Region: Choose a region close to your devices for lower latency (e.g.,
us-east-2)Bucket name: Must be globally unique (e.g.,
rpi-zero2w-toolhead-camera)Object Ownership: ACLs disabled (recommended)
Block Public Access settings:
For enhanced security, keep “Block all public access” enabled (recommended)
If you need to access images from external systems, use IAM-based access controls or generate signed URLs rather than making the bucket public
Only uncheck public access if you fully understand the security implications and need publicly accessible URLs (as shown in the screenshot)
Bucket Versioning: Can be left disabled by default. Enable if you want to keep multiple versions of files (less applicable when uploading timestamped images as this camera does)
Default encryption: Enable Server-side encryption with Amazon S3 managed keys (SSE-S3)
Optional: Enable Public Read Access
⚠️ Security Warning: Enabling public read access means anyone with knowledge of your object URLs can download images from your bucket. This may lead to unintended data exposure and unexpected AWS data transfer charges. Only enable this if you specifically need publicly accessible URLs and understand these implications.
If you unchecked “Block all public access” and want to allow public read access to images (useful for accessing images directly via URL without authentication), add the following bucket policy:
Go to your bucket → Permissions tab → Bucket policy
Click Edit and paste the following policy (replace
your-bucket-namewith your actual bucket name):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicRead",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::your-bucket-name/*"
}
]
}
Click Save changes
This policy allows anyone to read (download) objects from your bucket via their public URLs. The IAM user credentials are still required for uploading and deleting objects. Note: Anyone who knows or guesses your object URLs can access them without authentication.
3. Create IAM Credentials#
Create AWS IAM credentials with S3 access permissions. Follow the official guide:
Detailed step-by-step instructions:
Navigate to IAM:
In the AWS Console, search for “IAM” in the top search bar or go to the Services menu → Security, Identity, & Compliance → IAM
Create a new user:
In the left sidebar, click Users
Click the Create user button (orange button in top right)
Enter a user name (e.g.,
a1-cam-user)Click Next
Set permissions:
Select Attach policies directly
Do not select any AWS managed policies (we’ll add a custom policy next)
Click Next
Review and click Create user
Add custom inline policy:
After creating the user, click on the user name to open the user details
Click on the Add permissions dropdown → Create inline policy
Click on the JSON tab
Replace the default policy with the following (based on Issue #159):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListObjectsInBucket",
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::your-bucket-name"
]
},
{
"Sid": "AllObjectActions",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::your-bucket-name/*"
]
}
]
}
Replace
your-bucket-namewith your actual bucket name (e.g.,rpi-zero2w-toolhead-camera)Click Next
Enter a policy name (e.g.,
a1-cam-s3-access)Click Create policy
Create access keys:
Still on the user details page, click the Security credentials tab
Scroll down to Access keys section
Click Create access key
Select Application running outside AWS as the use case
Click Next
(Optional) Add a description tag (e.g., “A1 Mini Camera Raspberry Pi”)
Click Create access key
IMPORTANT: You’ll see your
AWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEYdisplayedClick Download .csv file or copy both values immediately - they will only be shown once!
Save these credentials securely in a password manager
Click Done
Security best practices:
Revoke credentials immediately if compromised
Never commit credentials to version control
The a1_cam device generates URLs like:
https://{BUCKET_NAME}.s3.{AWS_REGION}.amazonaws.com/{object_name}
If you need to configure additional bucket policies, see:
boto3 Setup#
The device uses boto3, the AWS SDK for Python, to upload images to S3.
Installation#
boto3 is included in the device requirements.txt and will be installed when you follow the dependency installation instructions below.
Configuration#
The device code explicitly passes AWS credentials to boto3 from the my_secrets.py file. This approach keeps all credentials in one place and avoids the need to configure ~/.aws/credentials on the Raspberry Pi.
Add the following to your my_secrets.py file (see Secrets section below for creating this file):
AWS_ACCESS_KEY_ID = "your-aws-access-key-id"
AWS_SECRET_ACCESS_KEY = "your-aws-secret-access-key"
AWS_REGION = "us-east-2" # or your chosen region
BUCKET_NAME = "rpi-zero2w-toolhead-camera" # or your bucket name
IMAGE_QUALITY = 85 # JPEG quality (1-100). Lower = smaller file size. 85 gives ~2-3 MB images
Image quality settings:
IMAGE_QUALITYcontrols JPEG compression (1-100 scale)Default of 85 produces ~2-3 MB images (down from ~35 MB at quality 90)
Lower values reduce file size but may decrease image clarity
Recommended range: 75-90 depending on your needs
The device.py code passes these credentials directly to boto3.client():
s3 = boto3.client(
"s3",
aws_access_key_id=AWS_ACCESS_KEY_ID,
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
region_name=AWS_REGION,
)
Security considerations:
The
my_secrets.pyfile stores credentials in plaintext. Ensure proper file permissions:chmod 600 my_secrets.pyKeep your Raspberry Pi login credentials secure and use SSH key authentication
Consider restricting SSH access and using fail2ban or similar tools
While boto3 also supports reading credentials from
~/.aws/credentialsor environment variables, this implementation explicitly passes them to keep all device secrets centralized inmy_secrets.py
Additional Resources#
MQTT Setup#
The A1 Mini camera uses MQTT for hardware-software communication. For comprehensive MQTT setup instructions, refer to the AC Microcourses documentation:
MQTT Credentials#
The device requires MQTT connection details in the my_secrets.py file (see Secrets section below for setup):
MQTT_HOST- Your MQTT broker host (e.g., HiveMQ Cloud)MQTT_PORT- Usually 8883 for TLS-encrypted connectionsMQTT_USERNAME- Your MQTT usernameMQTT_PASSWORD- Your MQTT passwordDEVICE_SERIAL- A unique identifier for this camera deviceCAMERA_READ_TOPIC- Topic for receiving capture commands (e.g.,rpi-zero2w/still-camera/request/{DEVICE_SERIAL})CAMERA_WRITE_TOPIC- Topic for publishing image URIs (e.g.,rpi-zero2w/still-camera/response/{DEVICE_SERIAL})
MQTT Library#
The device uses paho-mqtt, the standard Python MQTT client library. It’s included in the device requirements.txt.
Quick Setup (Automated)#
For a streamlined setup process, use the provided setup.sh script:
bash setup.sh
This script automates all the steps described in the sections below. Note: If this script and README diverge, README is authoritative. See the Codebase, Secrets, and Dependencies sections for manual step-by-step instructions.
Codebase#
Update the system package list via:
sudo apt update
Optionally, upgrade the upgrade-able packages to the latest versions (-y flag is used to automatically answer “yes” to any installation prompts):
sudo apt upgrade -y
Ensure that git is installed:
sudo apt-get install git -y
Clone the repository to your Raspberry Pi Zero 2W device via HTTPS (allows for git pull to work without needing to enter credentials each time):
git clone https://github.com/AccelerationConsortium/ac-dev-lab.git
Navigate to the same directory as this README file:
cd /home/ac/ac-dev-lab/src/ac_training_lab/a1_cam/
Secrets#
Make a copy of my_secrets_example.py called my_secrets.py:
cp my_secrets_example.py my_secrets.py
Fill in the necessary information. Keep in mind this will store the credentials in plain-text format, so try to keep your Pi login secure and restrict the access scope for the credentials as much as possible (e.g., topic filtering for MQTT and bucket policies for S3).
Dependencies#
If not already installed (not pre-installed on RPi OS Lite), install picamera2 via:
sudo apt install python3-picamera2 --no-install-recommends
libcamera should be automatically installed after installing picamera2. Otherwise, one would use sudo apt install -y python3-libcamera (libcamera also does not come preinstalled on RPi OS Lite versions).
Also install FFmpeg:
sudo apt install ffmpeg --no-install-recommends
Use the venv command to create a virtual environment to a new folder venv with the --system-site-packages flag so that it can use the picamera2 and libcamera libraries and activate the environment via the following commands:
python3 -m venv --system-site-packages venv
source venv/bin/activate
While one could use the built-in Python installation (this device is intended to be run via a single top-level script, the RPi device (in our case RPi Zero 2W) requires minimal setup (i.e., can easily be reflashed), and the RPi device is intended for a single purpose with a single set of requirements (i.e., a “point-and-shoot” camera)), the extra steps involved to make this work are as equally onerous as using a venv [context], hence we only include instructions assuming a venv.
Next, install the requirements via:
pip install -r requirements.txt
“Local” (i.e., not RPi OS) OS Development#
For local development (e.g., on your PC rather than the Raspberry Pi to make version control easier) with a dummy version of picamera2 (very minimal mock package), while in the same folder as this README file, additionally run pip install -e ./dummy_pkg/. WARNING: do not install this on the Raspberry Pi for the toolhead camera – the imports will overlap with the “real” system packages picamera2 and libcamera.
Running the Device#
To start the device manually and ensure that it’s functioning normally, run:
python3 device.py
Testing the Camera#
To verify the camera is working, you have two options:
Option 1: Jupyter Notebook (Recommended)
Use the provided test_camera.ipynb notebook to interactively test the camera:
The notebook allows you to:
Configure your MQTT credentials
Send a capture command
Download and display the captured image
Option 2: Python Script
Run _scripts/client.py locally (e.g., on your PC), ensuring you have the same credentials in a my_secrets.py located in the _scripts directory as you do on the RPi. This script will request the latest image from the device and save it to your local machine.
Workflow Example#
Here’s how the complete image capture workflow operates:
Setup: Configure AWS S3 bucket and credentials, set up MQTT broker
Image capture request: Orchestrator sends
{"command": "capture_image"}via MQTT toCAMERA_READ_TOPICDevice captures: Raspberry Pi takes a photo using the camera
Upload to S3: Device uploads image to S3 bucket using boto3
Respond with URI: Device publishes S3 URI back to orchestrator via MQTT on
CAMERA_WRITE_TOPICAccess image: Orchestrator can download image from S3 or store URI in database
For implementation details, see Issue #159.
Automatic startup#
To create the file, run nano (or other editor of choice):
sudo nano /etc/systemd/system/device.service
Copy the following code into the file (right click to paste), save it via Ctrl+O and Enter and exit via Ctrl+X:
[Unit]
Description=Start picam device.py script
After=network-online.target
Wants=network-online.target
[Service]
# Launch the device script (adjust the path as needed)
WorkingDirectory=/home/ac/ac-dev-lab/src/ac_training_lab/a1_cam
# Best to specify the full path to the Python interpreter or use ExecSearchPath
ExecStart=/home/ac/ac-dev-lab/src/ac_training_lab/a1_cam/venv/bin/python3 device.py
# Restart whenever the script exits ('always' because sometimes it throws an error but still exits gracefully)
Restart=always
RestartSec=10
# Limit restart attempts to avoid a rapid infinite loop (e.g., up to max 9 times per day, assuming a StartLimitBurst of 3, 28800 seconds == 8 hours, "h" syntax wasn't working on RPi, so using seconds)
StartLimitInterval=3600
StartLimitBurst=3
# Allow up to 60 seconds for the script to start properly
TimeoutStartSec=60
[Install]
WantedBy=multi-user.target
Run:
sudo systemctl daemon-reload
sudo systemctl enable device.service
Run:
sudo crontab -e
Add the following at the end of the crontab file:
#
# Restart at 2 am, local time (set up during flashing, or specified manually via e.g., `sudo timedatectl set-timezone America/New_York`)
0 2 * * * /sbin/shutdown -r now
You can manually start the service by running:
sudo systemctl start device.service
This command tells systemd to run your service immediately (as if it had been triggered at boot). To check its status, use:
sudo systemctl status device.service
To view any logs:
sudo journalctl -u device.service -f
Starting the service with systemd is recommended since it applies all the configured options (dependencies, restart behavior, etc.).
For more details, see the systemctl(1) manual.
To stop the service (for example, while you work on fixing it / pulling new changes), run:
sudo systemctl stop device.service
This command stops the running instance of the service immediately. If you also want to prevent it from starting at boot until you’ve fixed it, you can disable it with:
sudo systemctl disable device.service
To get it to reflect the new changes, run:
sudo systemctl daemon-reload
You can list all available service unit files by running:
systemctl list-unit-files --type=service
This will display a list of service files along with their state (enabled, disabled, static, etc.). It shows unit files from all directories (such as /etc/systemd/system, /usr/lib/systemd/system, and /run/systemd/system).
For a list of all loaded (active or inactive) service units, you can use:
systemctl list-units --all --type=service
For more details on managing services, check out the systemctl(1) manual [transcript].