
Heeley City Farm
Farm welfare is usually checked by people on rounds, and nobody can watch five enclosures round the clock. I built sensors that judge welfare on the spot every 30 seconds, a camera that deletes any photo with a person in it, and a public dashboard anyone can open.
Licensed under CC BY 4.0.

The problem
Farm welfare is usually checked by people on rounds, now and then, and what they notice depends on who is looking. Nobody can watch five enclosures round the clock. Most monitoring is built for large commercial farms or for single animals wearing sensors, which leaves community farms like this one out, and the farm wanted live welfare readings that anyone visiting could see.
Welfare is normally assessed through periodic manual inspection, which is labour-intensive, intermittent and subjective. Existing monitoring targets large commercial operations or individual animals with wearables, so community and urban farms are underserved. The requirement was live, public welfare data at the point of visit across five enclosures: goats, sheep, cattle, ponies and chickens.
What I did
I built five weatherproof sensor units and installed one in each enclosure. Each judges welfare against the Five Freedoms every 30 seconds, deletes any photo with a person in it, and feeds a public dashboard that needs no login. Offline, I trained vision models on the photos to see whether a camera could spot what the sensors notice.
I assembled and installed five Raspberry Pi 4B units and wrote the Python polling service that classifies each reading against species ranges on the device. A YOLOv8n person check deletes frames before any path is recorded. Readings land in two Supabase tables, which a TypeScript Edge Function serves to a static public dashboard. An offline PyTorch pipeline trains and compares EfficientNet-B0, ResNet-50 and Swin Tiny on the overhead images.
How it works
Measure
A weatherproof box in each of the five enclosures reads temperature, humidity, light, movement and sound every 30 seconds.
Judge on the spot
The box checks each reading against what that species needs, using the Five Freedoms, and marks each one good or worth a look.
Leave people out
A camera takes overhead photos for later. If a person is in one, the box deletes it before it is ever saved.
Keep a copy
Every reading is written to the box itself and sent online when the connection allows, so a dropped signal loses nothing.
Show everyone
A public dashboard shows each enclosure's status and readings, refreshed every 30 seconds, for staff and visitors alike.
Learn later
On my own computer I train vision models on the photos, to see whether a camera can spot what the sensors notice.
On each of the five units
01Sense
Five identical units, one per enclosure, each in an IP65 box with the camera looking through the clear lid.
- Raspberry Pi 4B 4 GB
- DHT22
- BH1750
- PIR, 7 m
- MAX9814 + MCP3008
- Camera Module 3
02Poll and classify
A Python service under systemd samples all five sensors every 30 seconds and marks each freedom good or attention against species ranges. Freedom 2 uses the temperature-humidity index, with an onset of 74 for poultry and 72 for the rest.
THI = (1.8T + 32) − (0.55 − 0.0055·RH)(1.8T − 26)- Python
- systemd
03Log locally
Every reading is appended to a log on the SD card before anything leaves the device, so connectivity loss costs nothing.
readings.jsonl04Person check
YOLOv8n through ONNX Runtime runs in 156 ms on the Pi 4B. A person above 0.5 confidence and the frame is deleted before any path is recorded. ONNX because the PyTorch runtime crashes on the Pi's Cortex-A72.
os.remove()- YOLOv8n
- ONNX Runtime
HTTPS REST, every 30 s
In the cloud
05Store
Each cycle writes the raw readings and the classification over HTTPS to two Supabase PostgreSQL tables.
raw_readings, classifications- Supabase
- PostgreSQL
06Serve
A TypeScript Edge Function merges both tables into one public JSON response that carries welfare indicators and environmental values only.
- Edge Function
- TypeScript
07Show
A static dashboard fetches that response every 30 seconds and draws a Five Freedoms card per enclosure. No login.
- Static site
SSH over Tailscale, from each unit
On my machine, offline
08Learn offline
Images and the local logs are pulled from each Pi over SSH through Tailscale. Labels are recomputed with the current classifier, models train in PyTorch on Apple Silicon, and predictions go back to the dashboard.
- Tailscale
- PyTorch
- EfficientNet-B0
- ResNet-50
- Swin Tiny
In numbers
88,000+
Polling cycles classified on the devices
30s
Polling loop and dashboard refresh
156ms
Person detection on a Pi 4B
13,692
Labelled overhead images, 1 to 21 July 2026
+0.732MCC
Best specialist: ponies, EfficientNet-B0
Single run, indicative
+0.540MCC
Best generalist: ResNet-50
Single run, indicative
≈80THI
Poultry enclosure at 29°C and 43% humidity
Onset for poultry is 74
≈7×
More training-eligible rows in the local logs than in the database
My part
Edge software
I wrote the polling service, the Five Freedoms classifier and the person check that run on each Raspberry Pi.
All of it
Cloud and dashboard
I set up the two database tables and the Edge Function, and built the public dashboard.
All of it
Vision models
I built the offline training pipeline, trained the models and ran every comparison.
All of it
Hardware
I assembled and installed all five weatherproof sensor units, one per enclosure.
All of it
Co-design
A workshop with farm staff, run by two of my co-authors from the university, set what to measure and how the dashboard should read.
Part of it
Research
I am first author of the paper, written with four co-authors and accepted at IoT 2026.
Part of it
Built with
- Raspberry Pi 4B
- Five edge units
- Python and systemd
- Polling service and classifier
- YOLOv8n and ONNX Runtime
- Person check on the device
- Supabase
- PostgreSQL tables and the Edge Function
- TypeScript
- The Edge Function
- Tailscale
- SSH access to the units
- PyTorch
- Offline training
- EfficientNet-B0, ResNet-50, Swin Tiny
- The models compared
In pictures



From the work
These are pictures of the project's own code running on simulated input. They show how it works, not what it found.





Where it stands
The units have been monitoring the farm since July 2026, with more than 88,000 polling cycles classified. The paper was accepted at IoT 2026 in Newcastle.
What it found
The chickens were too warm. A typical reading of 29°C and 43% humidity gives a heat stress score of about 80, against a threshold of 74 for poultry. The dashboard flagged it continuously, and the staff were made aware through it.
Four of the five freedoms flagged nothing in 21 days, apart from the poultry heat.
Movement was the only thing that varied, so it was the only thing worth teaching a camera. The best model, for the ponies, scored +0.732 on a scale where 0 is chance and 1 is perfect. That was a single run, so read it as a promising sign rather than a result.
Every model result here comes from a single run, so each is indicative only.
Pre-training on a public overhead pig dataset, not farm data, put EfficientNet-B0 at 91.8% validation accuracy. Swin Tiny reached 74.6% after collapsing at epochs 8 to 9, and ResNet-50 64.9%.
Freedom 4, movement, was the only freedom with variation: 1,200 attention rows. Swin Tiny trailed ResNet-50 on three of five specialists, so the idea that a transformer reads a whole enclosure better was not supported here.
A CNN-LSTM over five consecutive chicken frames reached +0.416 against +0.379 for one frame. Adding the PIR readings gave +0.417, no further value. In embedding space, attention and good images sit in essentially the same place (d = 0.102), so Freedom 4 is not something a single frame can show.
What went wrong
A filter that only knew animals from the side
I tried a ready-made detector to throw out photos with no animals in them. It had learned animals side-on, so looking down from above it rejected over 89% of my images. I switched it off.
YOLOv8n, trained on side-on, ground-level COCO images, rejected over 89% of overhead frames across all enclosures. It is disabled, and an overhead animal classifier is future work. The person check on the device is a different task and works.
A smudged lens
From 13 July the chicken camera's lens was partly obscured, and 63% of the chicken photos in the second training round were affected.
A camera_clean column in the training data records image quality, so the effect can be audited on its own. ResNet-50 still reached +0.656 on chickens; convolution may cope better with uniform haze.
The goats could not be learned
The goat pen raised only three movement alerts in the training data. No model learns from three examples, so every one scored zero.
Three attention rows in training. MCC 0.0 for EfficientNet-B0, Swin Tiny and ResNet-50 alike.
A wild pigeon triggered the PIR in the poultry enclosure once, which is the limit of a fixed motion sensor in an open pen.
A different species was in the sheep pen from 8 July 2026, so some labels stopped matching the animals in the photos.
Two units lost their connection now and then. The local logs kept everything, with about seven times more training-eligible rows than the database.
What comes next
A longer deployment, so the other freedoms get a chance to raise alerts, and more data, so the goats can be learned. Then an overhead animal detector to replace the one I switched off, and the sequence model at a larger scale.
A second phase across more farms has been discussed.