Python client for a Raspberry Pi Zero W with an AHT20 temperature/humidity sensor. The Pi reads temperature and humidity on a polling interval and sends JSON readings to a backend API using bearer-token authentication.
This is the device-side repo for a larger temperature monitoring project. The Flask API/dashboard lives in a separate repo. This client can point at either a local development API or the production API by switching the active .env file.
Current test device:
- Raspberry Pi Zero W
- Raspberry Pi OS Lite
- AHT20 temperature/humidity sensor over I2C
I2C wiring:
- VIN → 3.3V
- GND → GND
- SCL → GPIO 3 / Pin 5
- SDA → GPIO 2 / Pin 3
run.py:
- Loads config from
.env - Initializes the AHT20 sensor
- Reads temperature and humidity
- Builds a JSON payload
- Sends it to the configured API endpoint
- Logs failures and keeps retrying if the API is unavailable
Successful sends are intentionally quiet. Failed sends are logged and retried on the next polling interval.
Example payload:
{
"type": "temp_and_humidity_reading",
"timestamp": 1718840000.123,
"device_id": "pi_zero_001",
"payload": {
"temperature_f": 72.41,
"humidity_percent": 54.82
}
}.
├── run.py
├── requirements-dev.txt
├── requirements.lock.txt
├── requirements.txt
├── scripts/
│ ├── log_sensor_overnight.py
│ ├── read_sensor_loop.py
│ └── use-env.sh
├── src/
│ └── utils/
│ ├── logging.py
│ ├── payload.py
│ ├── sensor.py
│ └── transmitter.py
└── tests/
├── test_payload.py
├── test_run_config.py
├── test_sensor.py
└── test_transmitter.py
Main files:
run.py— main polling loopsrc/utils/sensor.py— initializes and reads the AHT20 sensorsrc/utils/payload.py— builds the backend API payloadsrc/utils/transmitter.py— sends readings to the APIsrc/utils/logging.py— configures Loguru loggingscripts/use-env.sh— switches between local and production configsscripts/read_sensor_loop.py— quick live AHT20 hardware checkscripts/log_sensor_overnight.py— long-running AHT20 hardware logging scripttests/test_*.py— pytest coverage for sensor, payload, transmitter, and startup config helpers
Create and activate a virtual environment:
python3 -m venv venv
source venv/bin/activateFor a reproducible Pi install, prefer the pinned lock file:
pip install -r requirements.lock.txtFor local development tests:
pip install -r requirements-dev.txtrequirements.txt lists the top-level runtime dependencies. requirements.lock.txt pins the full dependency set used for deployment. requirements-dev.txt is for local pytest tooling.
Create local environment files in the project root:
.env.local
.env.production
.env
.env.local and .env.production store the local and production configs. .env is the active config used by run.py and systemd.
Local development example:
API_URL=http://<backend-host>:5000/api/device/readings
AUTH_TOKEN=replace_me
DEVICE_ID=pi_zero_001
POLL_INTERVAL_SEC=60Production example:
API_URL=https://<your-domain>/api/device/readings
AUTH_TOKEN=replace_me
DEVICE_ID=pi_zero_001
POLL_INTERVAL_SEC=300Notes:
API_URLmust point directly to the backend/api/device/readingsendpoint.AUTH_TOKENmust match the backend’s bearer token.DEVICE_IDidentifies this Pi in the backend.POLL_INTERVAL_SECcontrols how often readings are sent.- Startup validates
DEVICE_ID,API_URL,AUTH_TOKEN, andPOLL_INTERVAL_SEC. .envfiles are ignored by Git.
Use the helper script to switch the active .env file.
./scripts/use-env.sh local./scripts/use-env.sh productionIf the service is running, restart it after switching:
sudo systemctl restart temp-sensor.serviceThe script prints the active API_URL, DEVICE_ID, and POLL_INTERVAL_SEC, while redacting AUTH_TOKEN.
Use this before testing the full API loop:
source venv/bin/activate
python -m scripts.read_sensor_loopThis uses the real AHT20 sensor through the shared sensor utility and prints readings every 10 seconds. It does not send anything to the API.
For a longer hardware logging check:
source venv/bin/activate
python -m scripts.log_sensor_overnightThis writes periodic readings to logs/overnight_test.log. These hardware scripts are not part of the pytest suite.
The pytest suite covers sensor reading conversion, payload shape, transmitter behavior, and startup config parsing.
source venv/bin/activate
pip install -r requirements-dev.txt
python -m pytestsource venv/bin/activate
python run.pyThe Pi can run the client as a systemd service.
Example service file:
[Unit]
Description=Temperature Sensor Monitor
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=tyspi-temp
WorkingDirectory=/home/tyspi-temp/temp-sensor
EnvironmentFile=/home/tyspi-temp/temp-sensor/.env
Environment="PYTHONUNBUFFERED=1"
ExecStart=/home/tyspi-temp/temp-sensor/venv/bin/python3 /home/tyspi-temp/temp-sensor/run.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.targetUseful commands:
sudo systemctl start temp-sensor.service
sudo systemctl stop temp-sensor.service
sudo systemctl restart temp-sensor.service
systemctl status temp-sensor.service --no-pagerEnable at boot:
sudo systemctl enable temp-sensor.serviceLogs are written with Loguru to:
logs/sensor_{time}.log
Warnings/errors also go to stderr, so manual runs and journalctl show useful output. Log files rotate at 2 MB and are retained for 7 days.
Key Python packages:
adafruit-circuitpython-ahtx0adafruit-blinkaadafruit-circuitpython-busdevicerequestspython-dotenvloguru
Tested on a Raspberry Pi Zero W with an AHT20 sensor. The client can send readings to the Flask backend over the local network and can run under systemd.
Production deployment is handled by the separate Flask backend repo. This client only needs the correct production API_URL and matching AUTH_TOKEN to send readings to the deployed API.