Siddhant Kumar
Project 030 · Agriculture

Livestock Health Collar.

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.

Advanced 18–26 hours 37 min read WearableAnimalSensors
Jump to source Bill of materials
Livestock Health Collar — reference build illustration MCU VCC · GND · SIG · NC
Difficulty
Advanced
Build time
18–26 hours
Indicative cost
₹3,400 – ₹4,600
Platform
ESP32 DevKit V1 (ESP-WROOM-32)
Category
Agriculture
Last updated
28 July 2026
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

SettingHow it is used
Dairy heat detectionCatching oestrus reliably is worth a great deal — a missed heat delays breeding by a full cycle.
Early illness detectionA rumination drop precedes visible illness by a day or more, buying critical treatment time.
Herd heat-stress managementHerd-level temperature and activity flag heat-stress events that cut production and welfare.
Calving predictionBehavioural changes before calving let staff be present for difficult births.
Extensive/rangeland monitoringWatching animals spread over large areas where no person can see them.
Learning behaviour classificationReal 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

AttributeValue
Difficulty levelAdvanced
Estimated completion time18–26 hours
Indicative build cost₹3,400 – ₹4,600
Primary disciplineAgriculture
Reference platformESP32 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.

ComponentKey specificationQtyApprox. 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 DAC1₹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 DMP1₹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 ID1₹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–SF121₹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 discharge2₹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 protection1₹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 frame1₹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 grid1₹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 lugs1₹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 jaw1₹400
Sealed IP68 enclosure
This will be knocked, rubbed and rained on for months. Pot everything.
Impact-resistant, potted, for a large animal1₹450
Small solar panel (optional)
Extends battery life toward indefinite for grazing animals in sun.
2 W, ruggedised, top-mounted1₹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

PartSpecificationSupplyInterfaceReference
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 DAC3.3 V logic / 5 V USBUART, SPI, I²C, I²S, CAN, PWMDatasheet
MPU-6050 6-axis IMU3-axis gyro ±250–2000 °/s, 3-axis accel ±2–16 g, 16-bit ADC, on-chip DMP2.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 ID3.0–5.5 V1-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–SF123.3 VSPIDatasheet
18650 Li-ion cell 3400 mAh + holder3.7 V nominal, 4.2 V full, 3400 mAh, ~12.6 Wh, 2 C discharge3.0–4.2 VHolder / spot-welded tabsDatasheet
TP4056 Li-ion charger + DW01 protection1 A programmable CC/CV charge to 4.2 V ±1 %, over-discharge and short protection4.5–5.5 V inmicro-USB / padsDatasheet
20 W 12 V polycrystalline solar panelVmp 17.5 V, Imp 1.14 A, Voc 21.6 V, 350 × 290 mm, aluminium frame12 V nominalMC4 / screw terminalsDatasheet
Double-sided perfboard 7 × 9 cm + headersFR-4, 0.1″ pitch, plated through-holes, 24 × 18 gridDatasheet
IP65 ABS junction enclosure 158 × 90 × 60 mmIP65, ABS, −20 to +80 °C, transparent lid, wall-mount lugsDatasheet

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.

LoadSupply railTypical current (mA)Notes
ESP32 DevKit V1 (ESP-WROOM-32)3.3 V logic / 5 V USB160Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly.
MPU-6050 6-axis IMU2.375–3.46 V (module 5 V tolerant)3.9Gyro bias drifts with temperature — re-zero at boot while the device is still.
DS18B20 waterproof temperature probe3.0–5.5 V1.5Dozens can share one GPIO — you address them by ROM code.
SX1278 LoRa 433 MHz module (Ra-02)3.3 V120Never power the radio without an antenna — the PA will destroy itself.
TP4056 Li-ion charger + DW01 protection4.5–5.5 V in1000Buy the version *with* protection ICs — the bare charger will over-discharge your cell.
20 W 12 V polycrystalline solar panel12 V nominal1140Rated 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.json under 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 dialout group: sudo usermod -aG dialout $USER and 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

LibraryWhy it is neededInstall
MPU6050_light / Adafruit MPU6050 1.3.xIMU register access, calibration and complementary-filter angles.Library Manager → "MPU6050_light" by rfetick
OneWire + DallasTemperature 2.3.x / 3.9.xBus enumeration and conversion commands for DS18B20 probes.Library Manager → "DallasTemperature" (pulls OneWire)
LoRa (sandeepmistry) 0.8.0SX127x radio configuration, packet TX/RX and callbacks.Library Manager → "LoRa" by Sandeep Mistry
Preferences (NVS) bundledWear-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.

Livestock Health Collar — system block diagramFunctional block diagram of the Livestock Health Collar system. SenseMPU-6050jaw + body motionDS18B20temperatureClassifyActivity modellie/stand/walk/grazeRumination + oestruson-collarSummariseDaily budgethours per activityBaseline compareper animalReportLoRa uplinkherd scaleFlag for checknot diagnosisaccel windowsbehavioursummary + alerts
Livestock Health Collar — system block diagram

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.

Livestock Health Collar — wiring schematicConnection schematic showing which controller pin drives each peripheral. Sensors / InputsControllerActuators / OutputsESP32 DevKit V1(ESP-WROOM-32)3.3 V logic / 5 V USBMPU-6050 IMUGPIO 21 / 22 / 33I²C, motion wakeDS18B20 temperatureGPIO 271-Wire, against theskinBattery/solar voltageGPIO 34Power monitoringSX1278 LoRaGPIO 5 18 19 23 / 26Herd-scale uplinkStatus LEDGPIO 2Very brief — batterymatters
Livestock Health Collar — wiring schematic
PeripheralPeripheral pinController pinSignal
MPU-6050 IMUSDA / SCL / INTGPIO 21 / 22 / 33I²C, motion wake
DS18B20 temperatureDATAGPIO 271-Wire, against the skin
Battery/solar voltagedividerGPIO 34Power monitoring
SX1278 LoRaSPI + DIO0GPIO 5 18 19 23 / 26Herd-scale uplink
Status LEDAnodeGPIO 2Very 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.
An ESP32 development board with the ESP-WROOM-32 module and USB connector
An ESP32 development board. On-collar classification and aggressive sleep are what make season-long, farm-scale operation possible. Photograph sourced from Wikimedia Commons — ESP32 Espressif ESP-WROOM-32 Dev Board.jpg. Reused under the licence stated on that page; please check it before republishing.

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.

Livestock Health Collar — architecture stackLayered architecture from hardware to user interface. Hardware layerESP32 DevKit V1 (ESP-WROOM-32) · MPU-6050 6-axis IMU · DS18B20 waterprooftemperature probeDriver layermpu · onewire · lorolib · preferencesApplication logicsampling loop · filtering · thresholds · state machinePresentation layerlocal display · serial console · logged output
Livestock Health Collar — architecture stack

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

plainActivity 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

plainDaily 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

plainPower 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.

Livestock Health Collar — firmware flowchartControl flow through the main program loop. Deep sleep; MPU wakes onmovementSample accelerometer, extractfeaturesClassify the activity windowRumination window?accumulate rumination timeaccumulate activityUpdate daily behaviour budgetEnd of day?compare to baselinesleepDeviation from baseline?flag animalnormalTransmit daily summary overLoRa, sleep
Livestock Health Collar — firmware flowchart

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.

cpplivestock-collar.ino
/* ═══════════════════════════════════════════════════════════════
   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); }
  }
}
classifyWindow() feature extractionOver a 5-second window the collar computes the variance and dominant frequency of the acceleration magnitude, plus an orientation proxy. These features separate the activities: rumination is a very regular ~1 Hz signal, walking is higher-frequency whole-body motion, lying and standing are low-variance in different orientations.
Rumination at ~1 Hz, low varianceThe chewing of cud is a distinctively regular jaw motion at roughly one chew per second. This narrow, steady frequency band with low overall variance is what distinguishes rumination from the more variable, higher-frequency motion of walking — and rumination time is the earliest health signal.
On-collar classification, not raw dataThe collar classifies each window locally and accumulates time budgets, transmitting only a tiny daily summary. Streaming raw accelerometer data over LoRa is impossible — the data rate is far too high and would flatten the battery in hours. On-device classification is what makes the whole system feasible.
endOfDay() per-animal baselineFlags are relative to each cow's own recent average, not an absolute threshold, because cows genuinely differ. A rumination time well below her own baseline flags possible illness; a big activity spike with reduced lying flags heat. Per-animal baselining is what makes the alerts specific.
Baselines updated only on normal daysThe baseline is not updated on a flagged day, so an illness or a heat does not drag the baseline toward the anomaly. This keeps the reference stable and the flags meaningful over time.
Oestrus: high walking + low lyingA cow in heat walks much more and lies much less than normal. Requiring both — elevated activity and reduced rest — makes the heat flag more specific than activity alone, cutting false positives from other causes of restlessness.
ill/heat framed as flagsThe outputs are "ill" and "heat" flags — prompts for a stockperson to check the animal, not diagnoses. A rumination drop says "look at this cow", and the person determines what is actually wrong. This is how the tool is meant to be used.

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.

DatasetSizeLicenceUse here
Your own labelled observationsA few hours per behaviourYoursThe decisive dataset — same collar, same placement, same animals.
Published cattle-behaviour accelerometer setsVariesResearchFeature 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.

MetricValueWhat 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-warning1–2 daysTypical 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.

TestWhat you should see
Observe an animal and compare the classificationThe reported activity matches what you observe (lying, standing, walking, grazing).
Watch during ruminationRumination is detected during actual cud-chewing bouts, with the ~1 Hz signature.
Accumulate a daily budgetA plausible budget — a healthy cow lies ~11 h, ruminates 7–9 h, grazes several hours.
Simulate reduced ruminationA rumination time below the baseline sets the illness flag.
Simulate an activity spikeElevated walking with reduced lying sets the oestrus flag.
Transmit a daily summary over LoRaThe compact summary arrives at the gateway across farm-scale distance.
Measure battery over a weekConsumption on track for season-long operation, with the MCU sleeping while the animal is still.
Check placement robustnessConsistent 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.

A field irrigation system watering crops
Livestock in a field. Automated monitoring catches the subtle early changes and silent heats that even good stockmanship misses in a large herd. Photograph sourced from Wikimedia Commons — Irrigation system.jpg. Reused under the licence stated on that page; please check it before republishing.

Troubleshooting: Common Errors & Fixes

Classification is unreliable

Likely cause. Inconsistent device placement.

Fix. The device must stay consistently positioned (under the jaw) with a counterweight. A device that rotates on the collar sees different motion for the same behaviour, so the features and classification become unreliable. Placement is the foundation everything else rests on.

Rumination is missed or over-detected

Likely cause. The chewing signature varies between animals; a fixed threshold is too rigid.

Fix. Train a small model on labelled rumination from your own animals rather than using fixed thresholds. Rumination is distinctive but individual, so per-animal or trained classification improves it substantially over hand-tuned rules.

Too many false illness/heat flags

Likely cause. No per-animal baseline, or baseline updated on anomalous days.

Fix. Flag relative to each animal's own baseline, and do not update the baseline on flagged days. Require sustained deviation, not a single window. For oestrus, require both elevated activity and reduced lying together.

Battery drains too fast

Likely cause. MCU not sleeping while the animal is still, or classifying too often.

Fix. Use the accelerometer motion interrupt to deep-sleep the MCU when the animal is still (which is many hours a day). Classify in bursts, not continuously. Add solar for grazing animals. The MCU should be asleep the majority of the time.

LoRa summaries not received

Likely cause. Body absorption, range, or antenna position.

Fix. The animal's body absorbs RF, so position the antenna to radiate clear of it. Raise the gateway antenna. Increase the spreading factor for range. Farm-scale LoRa is achievable but antenna placement on a moving animal matters.

The sketch will not upload — "Failed to connect" or "avrdude: stk500_recv()"

Likely cause. The bootloader is not being reached: wrong port, wrong board, a serial monitor holding the port open, or a USB cable that only carries power.

Fix. Close every serial monitor, confirm Tools → Board and Port, and swap to a known data-capable USB cable. On an ESP32 hold BOOT while the IDE prints "Connecting…", then release. If a peripheral is wired to the UART pins (GPIO 1/3 on ESP32, D0/D1 on Uno) unplug it — it fights the programmer.

The board resets in a loop, or the serial monitor prints "Brownout detector was triggered"

Likely cause. The supply cannot deliver peak current. Wi-Fi transmit bursts, relay coils and servos all pull far more than their average draw.

Fix. Power peripherals from a separate regulated supply with a common ground rather than from the board 5 V pin. Add a 470–1000 µF electrolytic capacitor across the supply near the load, and use a real power adapter rather than a laptop USB port.

Serial monitor shows garbage characters

Likely cause. Baud rate mismatch between Serial.begin() and the monitor, or a floating/shared UART line.

Fix. Set the monitor to 115200 to match the sketch. If it still garbles, the crystal or the USB bridge is being confused by noise — shorten the cable and keep motor wiring away from the USB lead.

An I²C device is not detected

Likely cause. Wrong address, missing pull-ups, swapped SDA/SCL, or a bus too long for the pull-up value.

Fix. Run an I²C scanner sketch first — it should print the device address. Most breakout boards include 4.7 kΩ pull-ups, but if you have chained four of them the parallel resistance is too low; remove the pull-ups from all but one board. Keep the bus under 30 cm at 100 kHz.

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 a millis() 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_t where 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

Why is rumination such a good health signal?

Because it is tightly coupled to a cow's digestive and overall health and responds early to almost anything wrong. A healthy cow ruminates 7–9 hours a day; when she is becoming ill — developing mastitis, a metabolic disorder, an infection, or under significant stress — her rumination drops before she shows any outward sign. This gives a day or more of warning, which is why commercial monitoring systems are built around it. A sustained rumination drop is one of the most sensitive early warnings available, and detecting it from jaw movement on a collar is entirely practical.

Why detect heat (oestrus) at all?

Because for a dairy farm it is the single highest-value output. A cow must be bred during her roughly one-day heat, which recurs only every 21 days. Miss it and her pregnancy is delayed three weeks — costing lost milk and a longer calving interval, which adds up to a lot of money across a herd. Cows in heat become much more active, and that activity spike is detectable, catching heats — especially "silent" ones in high-yielding cows — that visual observation misses.

Why classify on the collar instead of sending raw data?

Because sending raw accelerometer data is impossible on this system. The data rate is far too high for a LoRa radio, and transmitting it would flatten the battery in hours. So the collar samples, extracts features, classifies each window into an activity, and accumulates time budgets locally, sending only a tiny daily summary of a few dozen bytes. On-device classification is not an optimisation here — it is the only thing that makes a season-long, farm-scale system possible.

How is this different from just watching the cows?

It watches all of them, all the time, and catches what a person cannot. A stockperson can watch a herd for a few hours and spot obvious problems, but cannot track every cow's rumination and activity continuously across day and night. The collar does exactly that, flagging the specific animals whose behaviour has changed — directing the stockperson's limited attention to the cows that need it, and catching the subtle early changes and silent heats that even good stockmanship misses in a large herd.

Can it diagnose what is wrong?

No, and it should not be expected to. It is a screening tool: a rumination drop says "check this cow", not "this cow has mastitis". The stockperson or vet then examines the animal and determines the actual problem. This is exactly how the commercial systems are used and marketed — as attention-directing tools that surface the animals worth examining, not as diagnostic devices. Framing it honestly as screening is important; treating a flag as a diagnosis would be wrong.

Will the battery really last a season?

With aggressive power management, yes — and with a small solar panel, potentially indefinitely in summer. The key is that a cow is stationary for many hours a day, and the accelerometer's motion interrupt lets the microcontroller deep-sleep during those periods, waking only to classify during activity. Classification runs in brief bursts and the radio transmits once a day. This stretches a couple of 18650 cells across a season, and grazing animals in sun with a small panel harvest more than they use. Without this power discipline, the device would need charging every night and would be useless.

References & Learning Resources

These are the primary sources worth reading in full. Manufacturer datasheets always outrank forum posts when the two disagree.

  1. Reith & Hoy, "Behavioral signs of estrus and the potential of fully automated systems for detection"Animal, 2018
  2. Beauchemin, "Invited review: Current perspectives on eating and rumination activity in dairy cows"Journal of Dairy Science, 2018
  3. Riaboff et al., "Predicting livestock behaviour using accelerometers: A systematic review"Computers and Electronics in Agriculture, 2022
  4. Stangaferro et al., "Use of rumination and activity monitoring for the identification of dairy cows with health disorders"Journal of Dairy Science, 2016
  5. MPU-6050 six-axis motion tracking device — datasheetTDK InvenSense
  6. LoRa and LoRaWAN — regional parametersLoRa Alliance