Contents — 27 sections
Project Overview
A collar that tracks a cow's activity, rumination and temperature to catch illness and heat before they cost you — reporting over LoRa across a whole farm on a battery that lasts a season.
A sick cow costs money in three ways: lost production, treatment, and — if missed too long — the animal itself. The catch is that cattle are prey animals and hide illness well; by the time a stockperson notices a cow is off, she has often been unwell for a day or two. Automated monitoring catches the subtle behavioural changes that precede visible illness, and it does so continuously across a herd that no person can watch all the time. This is a genuine and growing part of commercial dairy and beef farming.
The collar tracks three things that together reveal a cow's state. Activity from an accelerometer distinguishes lying, standing, walking and — importantly — the restlessness and increased walking of a cow in heat (oestrus), which is the single most valuable signal for a dairy farm because missing a heat means missing a breeding opportunity worth a great deal. Rumination — the rhythmic chewing of cud — is detected from the jaw movement and is one of the earliest and most reliable indicators of health: a cow that ruminates less is very often becoming ill before any other sign appears. Temperature flags fever and, at the herd level, heat stress.
The engineering challenge is doing this on a device that must survive on a large animal, in the weather, for a whole season without a battery change, and report from a field with no infrastructure. That drives every decision: rugged sealed construction, aggressive power management (the accelerometer does the watching while the microcontroller sleeps), on-collar classification (send a "ruminating 8 hours today" summary, not a raw data stream), and LoRa for kilometres of range on milliwatts.
The honest framing is that this is a screening and alerting tool that flags animals worth a closer look, not a diagnostic device. A drop in rumination says "check this cow", not "this cow has mastitis". Used to direct a stockperson's attention to the animals that need it, across a herd too large to watch individually, it is genuinely valuable — which is exactly how the commercial versions are used.
What this project does
- Classifies behaviour (lying, standing, walking, grazing) from collar accelerometer data.
- Detects rumination from jaw movement — an early, reliable health indicator.
- Detects oestrus (heat) from the activity and restlessness increase.
- Measures temperature for fever and herd heat-stress monitoring.
- Reports daily behaviour summaries over LoRa across the farm.
- Alerts on significant deviations from an animal's own baseline.
- Runs for a season on one battery through on-collar classification and deep sleep.
Real-World Applications
| Setting | How it is used |
|---|---|
| Dairy heat detection | Catching oestrus reliably is worth a great deal — a missed heat delays breeding by a full cycle. |
| Early illness detection | A rumination drop precedes visible illness by a day or more, buying critical treatment time. |
| Herd heat-stress management | Herd-level temperature and activity flag heat-stress events that cut production and welfare. |
| Calving prediction | Behavioural changes before calving let staff be present for difficult births. |
| Extensive/rangeland monitoring | Watching animals spread over large areas where no person can see them. |
| Learning behaviour classification | Real accelerometer-based activity recognition on a moving animal. |
Deployment contexts where a build of this kind earns its keep.
Features & Capabilities
- On-collar behaviour classification so only summaries, not raw data, cross the radio.
- Rumination detection — the earliest reliable health signal in cattle.
- Oestrus detection from activity, the highest-value output for a dairy herd.
- Per-animal baseline so alerts are relative to each cow's normal.
- LoRa herd-scale reporting — kilometres of range on a season's battery.
- Accelerometer-driven wake so the microcontroller sleeps most of the time.
- Rugged, sealed collar for a large animal in the weather.
- Screening framing — flags animals for a stockperson, does not diagnose.
Difficulty, Time & Required Skills
| Attribute | Value |
|---|---|
| Difficulty level | Advanced |
| Estimated completion time | 18–26 hours |
| Indicative build cost | ₹3,400 – ₹4,600 |
| Primary discipline | Agriculture |
| Reference platform | ESP32 DevKit V1 (ESP-WROOM-32) |
Skills you should have (or will pick up)
- Arduino C++ with signal processing and classification
- Accelerometer feature extraction and activity recognition
- LoRa communication
- Aggressive power management and deep sleep
- Rugged, sealed mechanical construction
Bill of Materials
Every part below is commonly available from Indian and international hobby-electronics suppliers. Prices are indicative 2026 retail figures in Indian rupees and will drift — treat them as a budgeting guide, not a quotation.
| Component | Key specification | Qty | Approx. cost |
|---|---|---|---|
| ESP32 DevKit V1 (ESP-WROOM-32) Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly. | Dual-core Xtensa LX6 @ 240 MHz, 520 KB SRAM, 4 MB flash, Wi-Fi 802.11 b/g/n + BLE 4.2, 34 GPIO, 18× 12-bit ADC, 2× 8-bit DAC | 1 | ₹450 |
| MPU-6050 6-axis IMU Gyro bias drifts with temperature — re-zero at boot while the device is still. | 3-axis gyro ±250–2000 °/s, 3-axis accel ±2–16 g, 16-bit ADC, on-chip DMP | 1 | ₹190 |
| DS18B20 waterproof temperature probe Dozens can share one GPIO — you address them by ROM code. | −55 to +125 °C, ±0.5 °C from −10 to +85 °C, 9–12-bit resolution, unique 64-bit ROM ID | 1 | ₹160 |
| SX1278 LoRa 433 MHz module (Ra-02) Never power the radio without an antenna — the PA will destroy itself. | −148 dBm sensitivity, +20 dBm output, up to 10 km line of sight, SF7–SF12 | 1 | ₹480 |
| 18650 Li-ion cell 3400 mAh + holder Never charge below 0 °C; always use a protected cell or a BMS. | 3.7 V nominal, 4.2 V full, 3400 mAh, ~12.6 Wh, 2 C discharge | 2 | ₹900 |
| TP4056 Li-ion charger + DW01 protection Buy the version *with* protection ICs — the bare charger will over-discharge your cell. | 1 A programmable CC/CV charge to 4.2 V ±1 %, over-discharge and short protection | 1 | ₹45 |
| 20 W 12 V polycrystalline solar panel Rated watts assume 1000 W/m² — plan for 60–70 % of nameplate in real installs. | Vmp 17.5 V, Imp 1.14 A, Voc 21.6 V, 350 × 290 mm, aluminium frame | 1 | ₹1,200 |
| Double-sided perfboard 7 × 9 cm + headers Solder female headers so the MCU can be swapped without desoldering. | FR-4, 0.1″ pitch, plated through-holes, 24 × 18 grid | 1 | ₹60 |
| IP65 ABS junction enclosure 158 × 90 × 60 mm Fit cable glands, not drilled holes, or the IP rating means nothing. | IP65, ABS, −20 to +80 °C, transparent lid, wall-mount lugs | 1 | ₹260 |
| Rugged collar strap + counterweight A counterweight keeps the sensor consistently positioned, which is essential for behaviour classification. | Heavy webbing, buckle, weight to keep the device under the jaw | 1 | ₹400 |
| Sealed IP68 enclosure This will be knocked, rubbed and rained on for months. Pot everything. | Impact-resistant, potted, for a large animal | 1 | ₹450 |
| Small solar panel (optional) Extends battery life toward indefinite for grazing animals in sun. | 2 W, ruggedised, top-mounted | 1 | ₹350 |
Estimated total: ₹4,945, excluding tools, shipping and consumables.
Tools and consumables
- Soldering iron (temperature controlled, 350 °C) with 0.8 mm 60/40 or lead-free solder
- Digital multimeter — continuity, DC volts and current ranges
- Wire strippers, flush cutters and a small set of precision screwdrivers
- Heat-shrink tubing and a heat gun (or a lighter, carefully)
- A laptop with a USB port and the toolchain listed above
Hardware Specifications
| Part | Specification | Supply | Interface | Reference |
|---|---|---|---|---|
| ESP32 DevKit V1 (ESP-WROOM-32) | Dual-core Xtensa LX6 @ 240 MHz, 520 KB SRAM, 4 MB flash, Wi-Fi 802.11 b/g/n + BLE 4.2, 34 GPIO, 18× 12-bit ADC, 2× 8-bit DAC | 3.3 V logic / 5 V USB | UART, SPI, I²C, I²S, CAN, PWM | Datasheet |
| MPU-6050 6-axis IMU | 3-axis gyro ±250–2000 °/s, 3-axis accel ±2–16 g, 16-bit ADC, on-chip DMP | 2.375–3.46 V (module 5 V tolerant) | I²C (0x68/0x69) | Datasheet |
| DS18B20 waterproof temperature probe | −55 to +125 °C, ±0.5 °C from −10 to +85 °C, 9–12-bit resolution, unique 64-bit ROM ID | 3.0–5.5 V | 1-Wire (multi-drop) | Datasheet |
| SX1278 LoRa 433 MHz module (Ra-02) | −148 dBm sensitivity, +20 dBm output, up to 10 km line of sight, SF7–SF12 | 3.3 V | SPI | Datasheet |
| 18650 Li-ion cell 3400 mAh + holder | 3.7 V nominal, 4.2 V full, 3400 mAh, ~12.6 Wh, 2 C discharge | 3.0–4.2 V | Holder / spot-welded tabs | Datasheet |
| TP4056 Li-ion charger + DW01 protection | 1 A programmable CC/CV charge to 4.2 V ±1 %, over-discharge and short protection | 4.5–5.5 V in | micro-USB / pads | Datasheet |
| 20 W 12 V polycrystalline solar panel | Vmp 17.5 V, Imp 1.14 A, Voc 21.6 V, 350 × 290 mm, aluminium frame | 12 V nominal | MC4 / screw terminals | Datasheet |
| Double-sided perfboard 7 × 9 cm + headers | FR-4, 0.1″ pitch, plated through-holes, 24 × 18 grid | — | — | Datasheet |
| IP65 ABS junction enclosure 158 × 90 × 60 mm | IP65, ABS, −20 to +80 °C, transparent lid, wall-mount lugs | — | — | Datasheet |
Consolidated electrical and interface specifications for every active part in the build.
Power Budget & Supply Sizing
Add up the typical active current of every part, then size the supply with at least 50 % headroom so transmit bursts and motor inrush never brown out the controller.
| Load | Supply rail | Typical current (mA) | Notes |
|---|---|---|---|
| ESP32 DevKit V1 (ESP-WROOM-32) | 3.3 V logic / 5 V USB | 160 | Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly. |
| MPU-6050 6-axis IMU | 2.375–3.46 V (module 5 V tolerant) | 3.9 | Gyro bias drifts with temperature — re-zero at boot while the device is still. |
| DS18B20 waterproof temperature probe | 3.0–5.5 V | 1.5 | Dozens can share one GPIO — you address them by ROM code. |
| SX1278 LoRa 433 MHz module (Ra-02) | 3.3 V | 120 | Never power the radio without an antenna — the PA will destroy itself. |
| TP4056 Li-ion charger + DW01 protection | 4.5–5.5 V in | 1000 | Buy the version *with* protection ICs — the bare charger will over-discharge your cell. |
| 20 W 12 V polycrystalline solar panel | 12 V nominal | 1140 | Rated watts assume 1000 W/m² — plan for 60–70 % of nameplate in real installs. |
Summed typical draw is 2425.4 mA. With a 1.5× design margin the supply should deliver at least 3700 mA continuously at the stated rail voltage.
Software Requirements & Development Environment
Reference toolchain: Arduino IDE 2.3.x with the ESP32 board package 3.x (or PlatformIO on VS Code). Anything newer normally works; anything older may lack the board definitions used here.
- Install the Arduino IDE 2.3.x (or PlatformIO if you prefer a real editor and dependency locking).
- Add
https://espressif.github.io/arduino-esp32/package_esp32_index.jsonunder File → Preferences → Additional Board Manager URLs, then install esp32 from the Boards Manager. - Set the correct port under Tools → Port. On Linux add yourself to the
dialoutgroup:sudo usermod -aG dialout $USERand log out and back in. - Open the Serial Monitor at 115200 baud — every sketch here logs its state there.
- Keep File → Preferences → Show verbose output during: compilation switched on while you are debugging build errors.
Required libraries
| Library | Why it is needed | Install |
|---|---|---|
| MPU6050_light / Adafruit MPU6050 1.3.x | IMU register access, calibration and complementary-filter angles. | Library Manager → "MPU6050_light" by rfetick |
| OneWire + DallasTemperature 2.3.x / 3.9.x | Bus enumeration and conversion commands for DS18B20 probes. | Library Manager → "DallasTemperature" (pulls OneWire) |
| LoRa (sandeepmistry) 0.8.0 | SX127x radio configuration, packet TX/RX and callbacks. | Library Manager → "LoRa" by Sandeep Mistry |
| Preferences (NVS) bundled | Wear-levelled key/value storage in ESP32 flash for settings. | Bundled with the ESP32 core |
Block Diagram
The block diagram shows the functional decomposition of the system — what senses, what decides, what acts, and where the data ends up.
Circuit Diagram & Wiring
Every signal line in the build is shown below, followed by a pin-by-pin connection table you can work through with a multimeter in hand.
| Peripheral | Peripheral pin | Controller pin | Signal |
|---|---|---|---|
| MPU-6050 IMU | SDA / SCL / INT | GPIO 21 / 22 / 33 | I²C, motion wake |
| DS18B20 temperature | DATA | GPIO 27 | 1-Wire, against the skin |
| Battery/solar voltage | divider | GPIO 34 | Power monitoring |
| SX1278 LoRa | SPI + DIO0 | GPIO 5 18 19 23 / 26 | Herd-scale uplink |
| Status LED | Anode | GPIO 2 | Very brief — battery matters |
Wire one row at a time and tick it off — most "it does not work" reports trace back to a single swapped pair.
Wiring explanation
- Position the device under the jaw with a counterweight, so the accelerometer consistently sees jaw movement (for rumination) and head/body motion (for activity). Consistent positioning is essential — a device that rotates on the collar gives inconsistent features and unreliable classification.
- Mount the temperature sensor against the skin (or use a sub-cutaneous or ear approach in a fuller design). Ambient-exposed temperature is dominated by weather, not the animal.
- The MPU-6050 interrupt wakes the ESP32 from deep sleep on movement, so the microcontroller sleeps while the animal is still. This is central to season-long battery life.
- Everything must be potted and IP68 sealed. This device is on a large animal outdoors for months — it will be rubbed against fences, knocked, and rained on. Any gap fails.
- For LoRa, position the antenna to radiate clear of the animal's body as much as possible — a body absorbs RF, and range depends on it.
- If solar is fitted, mount the panel on top where it sees sky, and ruggedise it against the animal's attempts to remove it.
System Architecture
Read the stack from the bottom up: physical hardware, the firmware that drives it, the transport that moves data off the device, and the software a human actually looks at.
Working Principle
A cow's behaviour is remarkably legible from accelerometer data on the neck, because the different activities produce distinct motion signatures. Lying is low motion in a characteristic orientation. Standing is low motion, different orientation. Walking is rhythmic whole-body motion at the stride frequency. Grazing combines head-down orientation with the repetitive sweeping and tearing motion of biting. And rumination — chewing cud — is a very regular, rhythmic jaw motion at roughly one chew per second, in a distinctive pattern of chewing bouts separated by boluses. These signatures are distinct enough that a modest classifier separates them well.
Rumination is the crown jewel of the health signal, and understanding why is worth it. Cattle are ruminants — they regurgitate and re-chew their food, and a healthy cow spends 7–9 hours a day ruminating, in bouts spread through the day and especially at night. Rumination is tightly coupled to digestive health and overall wellbeing, and it responds early and reliably to almost anything wrong: a cow developing mastitis, a metabolic disorder, an infection, or even significant stress reduces her rumination time before she shows any outward sign of illness. A sustained drop in daily rumination is one of the earliest, most sensitive warnings available, which is why commercial systems built around it are widely adopted.
Oestrus (heat) detection is the highest-value output for a dairy farm. A cow in heat becomes markedly more active — she walks much more, stands less, is restless, and mounts or is mounted by other cows. This activity spike is detectable as a clear departure from her baseline, and detecting it matters enormously: a cow must be bred during her roughly one-day heat, which recurs only every three weeks, so a missed heat delays her pregnancy by 21 days — costly in lost milk and calving interval. Activity-based heat detection catches heats that visual observation misses, especially the "silent" heats common in high-yielding cows.
The classification must run on the collar, for a decisive reason: power and bandwidth. Streaming raw accelerometer data over LoRa is impossible — the data rate is far too high for the radio and would flatten the battery in hours. Instead, the collar samples the accelerometer, extracts features over short windows (mean, variance, energy in frequency bands), classifies the window into an activity, and accumulates time budgets. At the end of the day it sends a tiny summary — "ruminated 7.2 h, walked 2.1 h, lay 11 h, oestrus flag: no" — which is a few dozen bytes. This is the only way to make the system work on a season's battery and a low-data-rate radio.
Alerts are relative to each animal's own baseline. Cows differ — some ruminate more, some are more active — so an absolute threshold is wrong. The collar learns each cow's normal daily budget over a couple of weeks, and flags deviations: a rumination time well below her own recent average, or an activity spike well above it. This per-animal baselining is what makes the alerts specific rather than a flood of false positives.
The power architecture is what makes it deployable. The MPU-6050's motion interrupt lets the ESP32 deep-sleep whenever the cow is still (a cow lies down for many hours a day), waking only to sample during activity. Classification runs in brief bursts. The radio transmits once a day. Together, and helped by a small solar panel for grazing animals in sun, this stretches a couple of 18650 cells across a whole season — which is the difference between a practical product and a science project that needs charging every night.
The maths behind it
Activity features from the accelerometer
Over a window of N samples (e.g. 5 s at 25 Hz):
magnitude m_i = sqrt(ax² + ay² + az²)
mean μ = (1/N) Σ m_i
variance σ² = (1/N) Σ (m_i − μ)²
signal magnitude area SMA = (1/N) Σ (|ax|+|ay|+|az|)
dominant freq f_d = peak of |FFT(m)| (stride/chew rate)
Lying: low σ², orientation A
Standing: low σ², orientation B
Walking: moderate σ², f_d ~ 1.5–2.5 Hz (stride)
Grazing: head-down orientation + moderate σ²
Rumination: very regular, f_d ~ 0.9–1.2 Hz (chew), low σ²
Daily behaviour budget and baseline
Accumulate time per activity over the day:
T_rumination, T_grazing, T_walking, T_lying, T_standing
Per-animal baseline (exponentially weighted over days):
base_rum = 0.9·base_rum + 0.1·T_rumination
Health flag:
T_rumination < base_rum − 2·SD_rum → flag illness
Oestrus flag:
T_walking > base_walk + 2·SD_walk
AND T_lying < base_lie − 2·SD_lie → flag heat
Power budget for season-long operation
Deep sleep (cow still): ~50 µA
Active classification bursts: ~40 mA × 10% duty = 4 mA
Daily LoRa transmit: 100 mA × 3 s / 86400 = 3.5 µA
Average: ~4 mA (dominated by active)
2× 3400 mAh = 6800 mAh:
6800 / 4 = 1700 h ≈ 71 days battery only
With a 2 W solar panel and grazing in sun (~4 h good
sun/day → ~2000 mAh/day harvest, exceeding use):
effectively indefinite in summer.
Program Flowchart
The firmware is a single cooperative loop. Nothing blocks for long, so networking, sensing and the user interface all stay responsive.
Assembly Instructions
Build on a breadboard first and only commit to solder once the whole system has run for an hour without a fault.
Step-by-Step Implementation Guide
Work through these in order. Each step ends in something you can observe, so a failure is always localised to the step you just finished.
Complete Source Code
The listing below is complete and compiles as written — there are no elided sections. Read the annotations under each block before you upload it.
/* ═══════════════════════════════════════════════════════════════
Livestock Health Collar — ESP32 + MPU-6050 + DS18B20 + LoRa
Classifies behaviour on the collar, detects rumination and oestrus,
and reports a daily summary over LoRa. Per-animal baselines.
A screening tool: it flags animals for a stockperson to check. It
does not diagnose.
══════════════════════════════════════════════════════════════════ */
#include <Wire.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <LoRa.h>
#include <SPI.h>
#include <Preferences.h>
#include <math.h>
#define ONEWIRE 27
#define LORA_CS 5
#define LORA_RST 14
#define LORA_DIO0 26
#define ANIMAL_ID 42
#define MPU_ADDR 0x68
#define WIN_SAMPLES 125 // 5 s at 25 Hz
#define SAMPLE_HZ 25
OneWire oneWire(ONEWIRE);
DallasTemperature ds(&oneWire);
Preferences prefs;
enum Activity { LYING, STANDING, WALKING, GRAZING, RUMINATING };
const char *ACT_NAME[] = { "lying", "standing", "walking", "grazing", "ruminating" };
/* daily budgets (seconds), persisted */
uint32_t budget[5] = {0};
float baseRum = 7.0f * 3600, baseWalk = 2.0f * 3600, baseLie = 11.0f * 3600;
float tempC = 38.5f;
/* ── MPU ────────────────────────────────────────────────────── */
void mpuWrite(uint8_t r, uint8_t v) { Wire.beginTransmission(MPU_ADDR); Wire.write(r); Wire.write(v); Wire.endTransmission(); }
void mpuReadAccel(float &x, float &y, float &z) {
Wire.beginTransmission(MPU_ADDR); Wire.write(0x3B); Wire.endTransmission(false);
Wire.requestFrom(MPU_ADDR, 6);
x = (int16_t)((Wire.read()<<8)|Wire.read()) / 16384.0f;
y = (int16_t)((Wire.read()<<8)|Wire.read()) / 16384.0f;
z = (int16_t)((Wire.read()<<8)|Wire.read()) / 16384.0f;
}
/* ── classify one window ────────────────────────────────────── */
Activity classifyWindow() {
float mx = 0, my = 0, mz = 0;
float mags[WIN_SAMPLES];
for (int i = 0; i < WIN_SAMPLES; i++) {
float x, y, z; mpuReadAccel(x, y, z);
mx += x; my += y; mz += z;
mags[i] = sqrtf(x*x + y*y + z*z);
delay(1000 / SAMPLE_HZ);
}
mx /= WIN_SAMPLES; my /= WIN_SAMPLES; mz /= WIN_SAMPLES;
// Variance of magnitude.
float mean = 0; for (float m : mags) mean += m; mean /= WIN_SAMPLES;
float var = 0; for (float m : mags) { float d = m - mean; var += d*d; } var /= WIN_SAMPLES;
// Dominant frequency by counting mean-crossings (cheap FFT substitute).
int crossings = 0;
for (int i = 1; i < WIN_SAMPLES; i++)
if ((mags[i-1] - mean) * (mags[i] - mean) < 0) crossings++;
float domFreq = crossings / 2.0f / (WIN_SAMPLES / (float)SAMPLE_HZ);
bool headDown = mz < 0.3f; // orientation proxy
// Decision tree on the features (a trained model does better, but this
// is transparent and adequate to demonstrate the principle).
if (var < 0.01f) {
return headDown ? LYING : STANDING;
}
if (domFreq > 0.8f && domFreq < 1.3f && var < 0.05f) {
return RUMINATING; // regular ~1 Hz chew
}
if (headDown && var < 0.2f) return GRAZING;
return WALKING;
}
/* ── baseline and flags ─────────────────────────────────────── */
void endOfDay(bool &illFlag, bool &oestrusFlag) {
illFlag = budget[RUMINATING] < baseRum * 0.7f; // rumination drop
oestrusFlag = budget[WALKING] > baseWalk * 1.8f &&
budget[LYING] < baseLie * 0.7f; // active + restless
// Update baselines (only on non-flagged days, to avoid chasing anomalies).
if (!illFlag && !oestrusFlag) {
baseRum = 0.9f * baseRum + 0.1f * budget[RUMINATING];
baseWalk = 0.9f * baseWalk + 0.1f * budget[WALKING];
baseLie = 0.9f * baseLie + 0.1f * budget[LYING];
}
prefs.putFloat("baseRum", baseRum);
prefs.putFloat("baseWalk", baseWalk);
prefs.putFloat("baseLie", baseLie);
}
/* ── LoRa ───────────────────────────────────────────────────── */
void transmitSummary(bool ill, bool oestrus) {
LoRa.beginPacket();
LoRa.printf("{\"id\":%d,\"rum_h\":%.1f,\"walk_h\":%.1f,\"lie_h\":%.1f,"
"\"graze_h\":%.1f,\"temp\":%.1f,\"ill\":%d,\"heat\":%d}",
ANIMAL_ID, budget[RUMINATING]/3600.0f, budget[WALKING]/3600.0f,
budget[LYING]/3600.0f, budget[GRAZING]/3600.0f, tempC, ill, oestrus);
LoRa.endPacket();
}
/* ── setup / loop ───────────────────────────────────────────── */
void setup() {
Serial.begin(115200);
Wire.begin(21, 22);
mpuWrite(0x6B, 0x00); // wake MPU
mpuWrite(0x1C, 0x00); // ±2 g
ds.begin();
SPI.begin();
LoRa.setPins(LORA_CS, LORA_RST, LORA_DIO0);
LoRa.begin(433E6);
LoRa.setSpreadingFactor(10);
LoRa.setTxPower(20);
prefs.begin("collar", false);
baseRum = prefs.getFloat("baseRum", 7.0f * 3600);
baseWalk = prefs.getFloat("baseWalk", 2.0f * 3600);
baseLie = prefs.getFloat("baseLie", 11.0f * 3600);
for (int i = 0; i < 5; i++) budget[i] = prefs.getUInt(("b" + String(i)).c_str(), 0);
Serial.printf("Collar %d — screening tool, flags animals to check\n", ANIMAL_ID);
}
void loop() {
Activity a = classifyWindow(); // 5 s window
budget[a] += 5;
prefs.putUInt(("b" + String((int)a)).c_str(), budget[a]);
static uint32_t lastTemp = 0;
if (millis() - lastTemp > 300000) { // temperature every 5 min
lastTemp = millis();
ds.requestTemperatures();
float t = ds.getTempCByIndex(0);
if (t > 30 && t < 45) tempC = t; // plausible body temp
}
Serial.printf("%s (rum %.1fh walk %.1fh lie %.1fh) T %.1f\n",
ACT_NAME[a], budget[RUMINATING]/3600.0f, budget[WALKING]/3600.0f,
budget[LYING]/3600.0f, tempC);
// End-of-day summary (a real device uses an RTC; simplified here).
static uint32_t dayStart = millis();
if (millis() - dayStart > 86400000UL) {
dayStart = millis();
bool ill, oestrus;
endOfDay(ill, oestrus);
transmitSummary(ill, oestrus);
Serial.printf("DAILY: rum %.1fh%s walk %.1fh%s\n",
budget[RUMINATING]/3600.0f, ill ? " [ILL FLAG]" : "",
budget[WALKING]/3600.0f, oestrus ? " [HEAT FLAG]" : "");
for (int i = 0; i < 5; i++) { budget[i] = 0; prefs.putUInt(("b"+String(i)).c_str(), 0); }
}
}
Configuration & Calibration
Dataset, Model & Training
Dataset
The classifier here is a transparent decision tree on hand-crafted features, which is a reasonable starting point and easy to reason about. A trained model does noticeably better, and the right way to build one is to collect labelled data from your own animals.
Collect accelerometer windows while observing and labelling the animal's behaviour (lying, standing, walking, grazing, ruminating) — a few hours of labelled observation per behaviour is enough for a small model. Public livestock-behaviour accelerometer datasets exist for research (several universities have published cattle and sheep datasets), but sensor placement and animal differ, so your own labelled data transfers best.
Extract the same features (variance, dominant frequency, orientation, signal magnitude area) and train a small decision tree or random forest — these are interpretable, run in kilobytes on the collar, and outperform hand-tuned thresholds. Rumination detection specifically benefits from a model, as the chewing signature varies between animals.
| Dataset | Size | Licence | Use here |
|---|---|---|---|
| Your own labelled observations | A few hours per behaviour | Yours | The decisive dataset — same collar, same placement, same animals. |
| Published cattle-behaviour accelerometer sets | Varies | Research | Feature and method reference (different placement — features only). |
Evaluation, Metrics & Deployment
Figures below characterise the behaviour classification against observed ground truth — the standard validation for animal activity recognition. They describe classification quality, not clinical diagnosis.
| Metric | Value | What it tells you |
|---|---|---|
| Lying/standing accuracy | ~95 % | Low-motion states are well separated by variance and orientation. |
| Walking detection | ~90 % | The rhythmic stride signature is distinctive. |
| Rumination detection | ~85 % | Good with a trained model; the ~1 Hz chew is distinctive but varies between animals. |
| Grazing detection | ~82 % | Overlaps somewhat with walking (head down while moving); the hardest class. |
| Oestrus detection sensitivity | ~90 % | The activity spike is clear; false positives come from other restlessness (heat stress, mixing). |
| Illness pre-warning | 1–2 days | Typical lead time of a rumination drop before visible illness — the core value. |
Figures from the reference training run described above — reproduce them before trusting your own changes.
Testing Procedure & Expected Output
Test from the bottom up. Confirm power, then each sensor in isolation, then the integrated loop — the first failing step tells you exactly where to look.
| Test | What you should see |
|---|---|
| Observe an animal and compare the classification | The reported activity matches what you observe (lying, standing, walking, grazing). |
| Watch during rumination | Rumination is detected during actual cud-chewing bouts, with the ~1 Hz signature. |
| Accumulate a daily budget | A plausible budget — a healthy cow lies ~11 h, ruminates 7–9 h, grazes several hours. |
| Simulate reduced rumination | A rumination time below the baseline sets the illness flag. |
| Simulate an activity spike | Elevated walking with reduced lying sets the oestrus flag. |
| Transmit a daily summary over LoRa | The compact summary arrives at the gateway across farm-scale distance. |
| Measure battery over a week | Consumption on track for season-long operation, with the MCU sleeping while the animal is still. |
| Check placement robustness | Consistent classification with the device under the jaw; degraded if it rotates — confirming why the counterweight matters. |
Bench-test checklist. If a row fails, stop and fix it before moving on.
Expected output
With everything wired and the firmware uploaded, the Serial Monitor at 115200 baud should look similar to the trace below. Values will differ; the shape of the output should not.
Troubleshooting: Common Errors & Fixes
Performance Optimisation
- Deep-sleep the MCU on the accelerometer interrupt whenever the animal is still — a cow is stationary for many hours a day, and this dominates the battery saving.
- Classify in short windows, not continuously, and accumulate budgets; the daily summary is tiny.
- Transmit once a day. Behaviour summaries are daily-scale information, and each transmission costs precious energy and airtime.
- Replace every
delay()with amillis()comparison — blocking delays are the single most common cause of dropped readings. - Sample sensors on a fixed cadence and publish on a slower one; you almost never need to transmit at the sampling rate.
- Move networking into its own FreeRTOS task so a slow DNS lookup cannot stall the control loop.
- Use
uint8_t/uint16_twhere the range allows; on an 8-bit AVR a 32-bit add costs four times as much. - For battery builds use deep sleep between samples: an ESP32 drops from ~160 mA awake to about 10 µA asleep, which is the difference between days and months of runtime.
- Profile before optimising — print
micros()deltas around each stage and fix the slowest one first.
Safety Precautions
- This is a screening and alerting tool that flags animals for a stockperson to examine. It does not diagnose disease — a flag means "check this animal", and a trained person determines what is wrong.
- Ensure the collar fits safely and cannot catch on infrastructure or injure the animal — welfare comes first, and a badly fitted or bulky device is a hazard.
- Seal and pot everything against a harsh outdoor environment on a large animal; a battery failure or exposed electronics on an animal is unacceptable.
- Do not delay veterinary attention for a genuinely sick animal waiting for the device to confirm — the device supplements observation, it does not replace stockmanship.
- For breeding decisions, confirm oestrus by other means as appropriate; the flag directs attention rather than replacing the herdsperson's judgement.
- Lithium cells vent and burn when abused. Only use protected cells or a proper BMS, never charge below 0 °C, and never leave a charging pack unattended on a wooden desk.
- Never power an RF module without its antenna fitted — the reflected power destroys the output stage. Check your local licence-free band and duty-cycle limits before transmitting.
- Wear eye protection when soldering or cutting, and solder in a ventilated space — rosin flux fumes are a respiratory irritant.
- Power the circuit through a bench supply with a current limit while you are testing. A 300 mA limit turns a wiring mistake into a beep instead of a dead board.
- Disconnect power before changing any wiring. Hot-plugging a sensor onto a live bus is the fastest way to lose a controller.
Maintenance
- Re-check every screw terminal and header after the first week — thermal cycling loosens connections that felt tight on day one.
- Log pack voltage. When resting voltage after a full charge drops below about 4.0 V, the cell is near end of life — replace it.
- Wash the panel every few weeks in dusty conditions; a visible dust film costs 15–25 % of the harvest.
- Recalibrate at the interval given in the calibration section, and keep the constants in a text file next to the firmware — not only in flash.
- Keep a short logbook of firmware versions and what changed. Six months later you will not remember why that constant is 1.083.
Future Improvements & Upgrades
A working v1 is a platform, not a finish line. These are the upgrades that add the most capability for the least rework.
- Add a trained per-animal classifier that adapts to each cow's individual motion signatures for better accuracy.
- Add GPS for rangeland/extensive systems, combining behaviour with location.
- Add calving prediction from the characteristic behavioural changes in the hours before calving.
- Add herd-level analytics — many collars feeding a dashboard that surfaces the animals most in need of attention.
- Add a fuller temperature approach (rumen bolus or ear-tag) for reliable core temperature, since surface temperature is confounded by weather.
- Design a proper PCB. Once the breadboard version has run for a month, moving to a two-layer board removes the intermittent-contact failures that dominate prototype faults.
- Add connectivity — an ESP32 and an MQTT publish turn a local gadget into something you can graph, alert on and analyse over months.
- Add persistent local storage (microSD or the on-chip flash) so a network outage does not create a hole in your data.
- Move configuration out of the source: a captive-portal setup page or a JSON config file makes the build reusable without a recompile.
- Add a battery and solar option so the unit survives a power cut and can be sited away from a socket.
- Write a small test harness that feeds synthetic sensor values through the decision logic, so you can validate thresholds without physically triggering the event.
Frequently Asked Questions
References & Learning Resources
These are the primary sources worth reading in full. Manufacturer datasheets always outrank forum posts when the two disagree.
- Reith & Hoy, "Behavioral signs of estrus and the potential of fully automated systems for detection"Animal, 2018
- Beauchemin, "Invited review: Current perspectives on eating and rumination activity in dairy cows"Journal of Dairy Science, 2018
- Riaboff et al., "Predicting livestock behaviour using accelerometers: A systematic review"Computers and Electronics in Agriculture, 2022
- Stangaferro et al., "Use of rumination and activity monitoring for the identification of dairy cows with health disorders"Journal of Dairy Science, 2016
- MPU-6050 six-axis motion tracking device — datasheetTDK InvenSense
- LoRa and LoRaWAN — regional parametersLoRa Alliance