Smart IoT Chicken Brooder System
Research hardware moved onto a working poultry farm, and onto national television
Sole Developer · Sep 2026 - Oct 2026 · Kilimo Hakika Limited

Featured on KTN News Kenya, How Technology Is Transforming Kenya's Poultry Industry, 13 October 2026.
Why chicks die
In Kenya most chickens die young. A four-year study of almost 2,000 households in Siaya found deaths made up 60 percent of all chickens leaving a household, more than were sold or eaten. Roughly 70 percent of scavenging indigenous chicks die before six weeks.
The warning signs are usually there hours or days ahead: a cold night, birds drinking less, a few extra deaths in the morning count. But the owner is often not on the farm, and a notebook cannot raise an alarm.
My final year project, MajiGuard, was an ESP32 water quality monitor with anomaly detection that could tell real contamination from a failing sensor and forecast a rise or fall in water quality. This is that work moved onto a brooder at Kilimo Hakika, with live birds in it.
What it measures, and what it does not
Air temperature and humidity from an SHT31. Water temperature from a DS18B20. pH, total dissolved solids and turbidity from the analog probes carried over from MajiGuard, including the same three-point pH calibration and Beer-Lambert turbidity maths, still reading their calibration constants from the same NVS namespace.
It does not measure ammonia, and that was a decision rather than an omission. Ammonia matters: above 20 to 25 ppm it raises respiratory disease risk, and at 50 to 75 ppm chicks lost 6 to 9 percent of body weight with mortality at 13.9 percent against 5.8 percent at zero. But cheap ammonia sensors drift badly in poultry houses, and a number you cannot trust is worse than no number. You can smell ammonia at about 20 ppm. The smell test plus a litter check is honest; a drifting sensor is not. If the farm ever heats with charcoal, a carbon monoxide alarm would matter more.
Thresholds that move with the birds
A brooder is not a river. The safe temperature changes as the chicks age, so the bands step down by day: 30 to 33°C in week one, 28 to 31 in week two, 25 to 29 in week three, 22 to 26 in week four. They also shift with humidity, dropping the target 1°C for every 5 points of relative humidity above 60.
Every one of those numbers is traced to a source. I wrote a 438-line literature review with over seventy citations before writing the thresholds, with a taxonomy for source types and explicit markers on anything I was inferring rather than citing.
The most useful thing that review turned up was not a number. It was that published sources disagree by up to 4°C mostly because they are measuring in different places: under the heater, at the brooder edge, at chick level, or in the open house. So the probe position has to be fixed, and the thresholds have to match where it actually sits. Without that, you are comparing readings against a band that was never meant for them.
The review ends with twenty numbered questions for a vet, and the thresholds in code are marked provisional until those are answered.
Alerts that have to justify themselves
The design rule was that a warning which cannot explain itself does not ship.
So an alert does not say "high mortality". It says: 11 died yesterday (0.6 percent) and 12 today (0.6 percent), and the warning line is 0.5 percent of live birds a day. The reader can check the arithmetic and decide. That mortality rule is the Dutch statutory avian influenza trigger for broilers, not a number I chose.
This matters because records-based alerts in the literature throw false alarms about 40 percent of the time. An alarm that wakes the owner at two in the morning and cannot say why is an alarm they will mute within a week. Every rule carries a floor for small numbers, and every alert names the figures that set it off.
Alerts run as a state machine: pending, raised after a sustain window, repeated every thirty minutes, then resolved with a "back to normal" push. Critical conditions can raise early. Informational ones appear in the app but never wake anyone. The sustain window for critical air temperature is ten minutes for chicks under a week old and fifteen after, because seven-day chicks at 16°C start losing body temperature within thirty minutes, while chicks older than five days held theirs through an hour at 10 to 20°C.
A forecast that grades itself
The next-hour temperature forecast is damped-trend Holt exponential smoothing, refitted every minute on the last two hours of readings, with the smoothing parameters re-chosen each run by backtest.
The part worth describing is not the model. It is that every prediction is stored and later back-filled with what actually happened, so the app shows the error measured on this farm rather than an accuracy I claimed. It refuses to quote a number until it has at least thirty samples over seven days. Until then it says so.
An early warning only fires when the recent slope is at least three standard errors from flat and the projection crosses the safe line by a real margin, sustained across three passes. Forecasting into an alarm is easy; forecasting into an alarm people still trust in week three is the actual problem.
Telling a broken sensor from a real emergency
This is the direct descendant of the MajiGuard work, and it is three rules rather than a model, because rules can be checked.
Frozen: the last thirty readings are identical, so the sensor or firmware is stuck. Jump: a change no brooder could physically make in a minute, so a loose wire or a moved probe. Wall mismatch: the box and the wall thermometer the worker logs by hand disagree by more than 2°C on two consecutive checks in the same direction. That last one is the drift check, and the manual reading is the reference.
A disconnected probe reads minus 127, which shows as dashes on the screen and a sensor error on the server, never as a fake emergency. A pH probe reading exactly zero is disconnected, not in acid.
There is also an unsupervised anomaly detector, River's HalfSpaceTrees, carried straight over from MajiGuard, one model per device learning what normal looks like for that box. It has no poultry validation and its own documentation warns it misses anomalies packed close in time, such as a slowly failing heater. So it only ever raises an in-app note, never a push, and only after three unusual readings in a row. It is the least trusted thing in the system and it is wired up accordingly.
Built for a farm with no internet
The farm has no connection. A MiFi gives the sensor box Wi-Fi. The managers' phones have patchy prepaid mobile data. Power is solar and unreliable.
So the sensor node keeps sixty readings in RAM and flushes them as one batch when the connection returns, and a sixty-second watchdog reboots the box if it hangs, because nobody is driving out to power-cycle it.
On the phone, every write lands in SQLite first and syncs later: after save, on reconnect, on foreground, and every sixty seconds regardless. The entire UI reads from the local store, so a worker standing in the brooder with no signal still sees correct bird counts. Records carry a UUID generated on the phone, so syncing twice cannot create duplicates.
The design brief described the moment the app gets used: standing in a hot, dusty brooder, often one-handed because the other hand has a feed bucket or a chick in it, sometimes in dim heat-lamp light, sometimes in bright sun, always rushed. That is why the temperature is set at 56sp, why the body typeface separates 1 from l from I and 0 from O, and why the dark-mode contrast was measured rather than eyeballed.
An advisor that refuses to guess
There is a farm advisor built on gpt-oss-120b, retrieving from the literature review with BM25 over its sections.
The constraints are the interesting part. It uses only the farm's own numbers and the retrieved research, never outside knowledge. It cites the sections it used, and those citations are verified against what was actually supplied, so a hallucinated reference gets dropped rather than displayed. It will not state a dose of any medicine or vaccine, and a regex check replaces the whole answer if one slips through. Any question touching deaths or sick birds ends by telling the farmer to call the vet. Where sources disagree, it says they disagree instead of picking one.
If the model cannot be reached, the app says so. There is no fallback that invents an answer.
I do not call it AI anywhere in the app. It is a language model I use, not one I trained, and the difference matters when a farmer is deciding whether to trust it.
Where it runs
On the same VPS as everything else I run for this client, behind the Caddy instance already serving their other sites. The API joins that shared network so Caddy can reach it; the database deliberately stays on its own private network and nothing is published on host ports.
Firebase delivers the push notifications and nothing else. The data lives on the VPS.
The deploy runbook labels every command with what it writes and whether its output is safe to paste into a chat, because the person running it at some point will not be me.
Honest status
One box, one house, one farm. It is genuinely deployed and running on a real brooder with live birds, which is the part that matters, but it is a single-site prototype rather than a product.
There is no outcome data yet. I am not claiming a mortality reduction, a percentage accuracy, or that any model was trained on this farm, because none of that exists. The farm is now measuring deaths per batch to find out whether it helps.
The thresholds are provisional until a vet signs them off, and the code says so in its version string.
Around 211 automated tests across the backend and the app, concentrated on sync, permissions, thresholds and the alert rules.