Contents β 26 sections
Project Overview
Turns a pheromone insect trap into a live counter, so a farmer sees a pest population rising day by day instead of discovering it after the damage is done.
Integrated pest management lives or dies on one number: how many pests are actually flying right now. The classic tool is a pheromone trap β a lure that mimics the female insect's scent, pulling males into a funnel or onto a sticky card. Counted regularly, trap catches tell a farmer exactly when a pest population is climbing toward the threshold at which spraying pays off, and just as importantly when it is not, so a spray can be skipped. The problem is the counting: someone has to walk to each trap, empty it, and tally the catch by eye, often across a large farm, and by the time they notice a spike the eggs are already laid. This project automates the count so the trap reports itself.
An optical gate across the throat of the funnel trap detects each insect as it falls through: an infrared beam that a passing body briefly interrupts. Each valid interruption increments a timestamped count, and simple signal shaping rejects the things that are not target insects β a leaf fluttering, a much larger bee, a raindrop. The counts accumulate into a daily catch, the daily catches into a trend, and the trend is exactly the signal IPM needs. An optional camera captures a periodic image of the catch so a human can verify the species and calibrate the counter, because no cheap gate can distinguish two similar moths on its own.
Because traps sit out in fields far from power and Wi-Fi, the counter runs on a small solar panel and reports over LoRa, waking only to log and transmit. Layered on top is a degree-day model: many pests emerge on a schedule set by accumulated warmth, so combining the live catch with a temperature-driven emergence estimate tells the farmer not just "the count is rising" but "this is the generation we expected, and it is early". The outcome is fewer, better-timed sprays β cheaper for the farmer, and gentler on the beneficial insects that indiscriminate calendar spraying destroys.
What this project does
- Counts insects falling through a pheromone trap using an infrared optical gate
- Rejects non-target events (debris, oversized insects, rain) by pulse shaping
- Timestamps each catch and accumulates daily counts and a multi-day trend
- Optionally photographs the catch periodically for species verification
- Combines catch data with a degree-day pest-emergence model
- Runs on solar + battery in a remote field and reports over LoRa
- Raises an alert when the catch crosses an action threshold
Real-World Applications
| Setting | How it is used |
|---|---|
| Orchard pest management | Codling moth, fruit fly and similar orchard pests, where trap-catch thresholds directly drive spray timing and one well-timed spray replaces several calendar ones. |
| Field crops (e.g. armyworm, bollworm) | Monitoring migratory or seasonal pests across large acreage where manual trap checks cannot keep up with a fast-building population. |
| Cooperative / area-wide IPM | Many traps across many farms feeding a shared map of pest pressure, so a whole region coordinates its response. |
| Research and extension | Entomologists and advisory services building long-term phenology records to refine emergence models and spray thresholds. |
Deployment contexts where a build of this kind earns its keep.
Features & Capabilities
- Automatic optical counting β no walking the traps to tally by eye
- Pulse-width discrimination to separate target insects from debris
- Daily catch and trend, the exact input IPM decisions need
- Degree-day emergence model fused with the live count
- Optional camera verification of species
- Solar + LoRa for season-long unattended field deployment
- Action-threshold alerts so a spray is timed, not guessed
Difficulty, Time & Required Skills
| Attribute | Value |
|---|---|
| Difficulty level | Intermediate |
| Estimated completion time | 10β16 hours |
| Indicative build cost | βΉ2,800 β βΉ4,200 |
| Primary discipline | Agriculture |
| Reference platform | ESP32 DevKit V1 (ESP-WROOM-32) |
Skills you should have (or will pick up)
- Building an infrared break-beam gate (emitter + detector) with clean thresholding
- Debouncing and pulse-width discrimination in firmware
- Degree-day accumulation and simple phenology models
- LoRa + solar design for remote field nodes
- Optional: triggering an ESP32-CAM capture and handling the image off-device
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 |
| TCRT5000 IR reflectance sensor module Ambient sunlight saturates it β shroud the sensor on outdoor robots. | 1β8 mm optimum sensing gap, 950 nm emitter, analogue + digital out | 1 | βΉ45 |
| DHT22 / AM2302 temperature + humidity sensor Needs a 4.7 kΞ© pull-up on the data line and 2 s between reads. | β40 to +80 Β°C Β±0.5 Β°C, 0β100 %RH Β±2 %, 0.5 Hz sample rate, single-wire digital | 1 | βΉ250 |
| ESP32-CAM (AI-Thinker) + OV2640 No USB-serial on board β needs an FTDI/CP2102 adapter to flash. | ESP32-S chip, 4 MB PSRAM, 2 MP OV2640 camera, microSD slot, on-board LED flash | 1 | βΉ620 |
| 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 |
| 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 |
| 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 |
| 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 | 1 | βΉ450 |
| Funnel/bucket pheromone trap body Match trap style to the target pest | Standard delta or bucket trap with a throat the gate can span | 1 | βΉ250 |
| IR emitter + phototransistor pair Modulated beam preferred to reject ambient IR | Matched 940 nm pair or a slotted photo-interrupter across the throat | 1 | βΉ90 |
| Pheromone lure (species-specific) Consumable β several per season | Sealed septum/rubber lure for the target pest; replace per label interval | 3 | βΉ900 |
| Weatherproof enclosure | IP65 for electronics; the trap body stays open to catch insects | 1 | βΉ400 |
Estimated total: βΉ5,180, 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 |
| TCRT5000 IR reflectance sensor module | 1β8 mm optimum sensing gap, 950 nm emitter, analogue + digital out | 3.3β5 V | Analogue + digital | Datasheet |
| DHT22 / AM2302 temperature + humidity sensor | β40 to +80 Β°C Β±0.5 Β°C, 0β100 %RH Β±2 %, 0.5 Hz sample rate, single-wire digital | 3.3β6 V | 1-wire proprietary | Datasheet |
| ESP32-CAM (AI-Thinker) + OV2640 | ESP32-S chip, 4 MB PSRAM, 2 MP OV2640 camera, microSD slot, on-board LED flash | 5 V in / 3.3 V logic | UART (programmer required), SPI, IΒ²C | 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 |
| 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 |
| 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 |
| 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 |
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. |
| TCRT5000 IR reflectance sensor module | 3.3β5 V | 20 | Ambient sunlight saturates it β shroud the sensor on outdoor robots. |
| DHT22 / AM2302 temperature + humidity sensor | 3.3β6 V | 1.5 | Needs a 4.7 kΞ© pull-up on the data line and 2 s between reads. |
| ESP32-CAM (AI-Thinker) + OV2640 | 5 V in / 3.3 V logic | 220 | No USB-serial on board β needs an FTDI/CP2102 adapter to flash. |
| SX1278 LoRa 433 MHz module (Ra-02) | 3.3 V | 120 | Never power the radio without an antenna β the PA will destroy itself. |
| 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. |
| 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. |
Summed typical draw is 2661.5 mA. With a 1.5Γ design margin the supply should deliver at least 4000 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 |
|---|---|---|
| WiFi (ESP32 core) bundled | Station/AP connection management for the ESP32. | Bundled with the ESP32 Arduino core |
| DHT sensor library 1.4.6 | Timing-critical driver for DHT11/DHT22. | Library Manager β "DHT sensor library" by Adafruit |
| LoRa (sandeepmistry) 0.8.0 | SX127x radio configuration, packet TX/RX and callbacks. | Library Manager β "LoRa" by Sandeep Mistry |
| ArduinoJson 7.x | Zero-allocation JSON serialisation and parsing. | Library Manager β "ArduinoJson" by Benoit Blanchon |
| Preferences (NVS) bundled | Wear-levelled key/value storage in ESP32 flash for settings. | Bundled with the ESP32 core |
| NTPClient / configTime bundled | Wall-clock time from an NTP server for timestamping. | Bundled (`configTime()` on ESP32) |
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 |
|---|---|---|---|
| IR gate detector | OUT | GPIO 34 (ADC/IRQ) | Beam-break pulse |
| IR gate emitter | drive | GPIO 25 | Modulated beam drive |
| DHT22 | DATA | GPIO 4 | Air temp/RH for degree-days |
| ESP32-CAM (opt) | trigger | GPIO 26 | Wake camera for verification shot |
| LoRa SX1276 | SCK/MISO/MOSI | GPIO 18/19/23 | SPI radio bus |
| LoRa SX1276 | NSS/RST/DIO0 | GPIO 5/14/2 | Chip-select, reset, IRQ |
| TP4056 | OUT | VIN / 3V3 reg | Solar-charged 18650 supply |
| Solar panel | +/β | TP4056 IN | 6 V panel β charger |
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
- Span the funnel throat with the IR emitter on one side and the detector on the other, aligned so the resting beam is strong and unbroken.
- Modulate the emitter (drive it at a few kHz) and detect only the modulated component, so daylight and other steady IR sources do not trip the gate.
- Keep the detector in shade under a small hood; a phototransistor staring at bright sky is saturated and blind to the beam break.
- The optional ESP32-CAM is a separate power domain β wake it only for a capture, since it draws far more than the counting node.
- Route the DHT22 in a shaded, ventilated spot so its temperature drives the degree-day model correctly.
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
The whole system rests on a pheromone trap's selectivity. The lure emits a synthetic copy of the target species' sex pheromone, so overwhelmingly it is the target pest that flies in. That means a crude counter β one that just detects "a body passed through" β still produces a meaningful, species-specific number, because the biology has already filtered the catch. The electronics only have to count what the chemistry has selected.
Counting is done with a break-beam gate at the trap's throat. An infrared beam crosses the funnel; when an insect falls through, it briefly blocks the beam and the detector output dips. The key to reliability is modulation: driving the emitter at a few kilohertz and looking only for that frequency at the detector, so steady daylight infrared β which is far brighter than the beam β is ignored. Without modulation a field gate is either blinded by sun or swamped with false counts; with it, the beam-break stands out cleanly against the ambient.
Not every beam-break is a target insect. A leaf, a raindrop, or a large non-target bee also blocks the beam, so the firmware discriminates by pulse width: a target moth interrupts the beam for a characteristic short duration as it falls, while a slow-crawling large insect, a resting leaf, or a persistent obstruction produces a much longer or permanent block. Counting only interruptions within a plausible duration window rejects most of the non-target events. This is imperfect β two similar-sized species cannot be separated optically β which is exactly why the periodic camera image exists: a human occasionally verifies that the count corresponds to the intended pest and adjusts the window if needed.
The count becomes actionable when paired with a degree-day model. Insect development is driven by accumulated warmth, not calendar days: a pest emerges after its life stage has banked a species-specific number of degree-days above a threshold temperature. By accumulating degree-days from the on-board temperature sensor, the node predicts when a new generation should appear, and the live trap catch confirms or refines that prediction. "The count is climbing and we are at the degree-day total where the second generation emerges" is a far stronger spray signal than either piece alone.
The maths behind it
Modulated beam-break detection
Emitter driven at f_mod (e.g. 4 kHz). Detector signal is
band-passed / synchronously sampled at f_mod.
beam_present when demodulated amplitude > A_hi
beam_broken when it falls below A_lo (hysteresis)
Ambient sunlight is broadband/DC β rejected by looking only
at the f_mod component. This is why a modulated gate works
in full sun where a DC gate fails.
Pulse-width discrimination
Measure how long the beam stays broken per event:
count it if t_min < t_break < t_max
(e.g. 2 ms < t_break < 40 ms for a falling moth)
t_break < t_min β electrical glitch β ignore
t_break > t_max β leaf/large insect/obstruction β ignore
and, if persistent, raise "gate blocked".
Degree-day accumulation (single sine / simple average)
Daily degree-days above a base temperature T_base:
DD_day = max(0, (T_max + T_min)/2 β T_base)
DD_total = Ξ£ DD_day from a biofix (first catch / date)
Emergence of the next generation is expected near a
species-specific DD_target. Combine with the live catch:
spray signal strongest when catch rising AND
DD_total near DD_target.
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.
Fit the trap and gate
Hang the pheromone trap where the target pest flies (canopy height for orchard moths, per the lure's guidance). Load the species-specific lure.
Mount the IR emitter and detector on opposite sides of the funnel throat so every insect dropping into the collection cup crosses the beam. Align until the resting beam gives a strong, steady detector reading.
Hood the detector against direct sun and rain.
Wire the electronics box
Put the ESP32, LoRa and battery in the IP65 box beside the trap. Bring the gate and DHT22 leads in through glands.
If using the camera, mount the ESP32-CAM looking at the collection cup and give it its own switched power so it only draws current during a capture.
Set up power and radio
Angle the solar panel to the sun, clear of the trap so servicing the trap does not disturb it. Route the LoRa antenna vertically, clear of metal.
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.
Tune the beam and thresholds
With no insect present, record the steady demodulated beam amplitude. Set the break/restore thresholds with hysteresis below it. Confirm a hand-wave across the beam registers as one clean event.
Set the pulse-width window
Drop a few sample insects (or a proxy) through the throat and record their beam-break durations. Set t_min/t_max to bracket them, excluding slow debris.
Count in an interrupt with discrimination
Handle the beam-break in an interrupt, measure the break duration, and only increment the daily count when the duration falls inside the target window; flag a persistent block as a fault.
cppgate-count.inovolatile uint32_t breakStart = 0; volatile uint32_t dailyCount = 0; volatile bool gateBlocked = false; #define T_MIN_US 2000UL // 2 ms β reject glitches #define T_MAX_US 40000UL // 40 ms β reject leaves/large insects #define BLOCK_US 2000000UL // 2 s persistent block = fault // Detector output goes LOW while the (demodulated) beam is broken. void IRAM_ATTR onEdge() { uint32_t now = micros(); if (digitalRead(PIN_GATE) == LOW) { // beam just broke breakStart = now; } else { // beam restored uint32_t dt = now - breakStart; if (dt > T_MIN_US && dt < T_MAX_US) dailyCount++; // one insect // dt <= T_MIN: glitch; dt >= T_MAX: debris/large β both ignored } } // Called periodically to detect a stuck/blocked gate. void checkBlocked() { if (digitalRead(PIN_GATE) == LOW && micros() - breakStart > BLOCK_US) { gateBlocked = true; // leaf lodged / lens fouled } }if (digitalRead(PIN_GATE) == LOW)The interrupt fires on both edges: a falling edge marks the beam breaking and starts the timer, a rising edge marks it restoring and ends it.if (dt > T_MIN_US && dt < T_MAX_US)Only interruptions whose duration matches a falling target insect are counted; electrical glitches (too short) and debris or large insects (too long) are discarded.void checkBlocked()A beam broken continuously for seconds is not an insect but a lodged leaf or a fouled lens, so it is flagged as a fault rather than silently miscounting.Accumulate degree-days, report and sleep
On a schedule, read temperature, update the degree-day total, roll the daily count over at midnight, check thresholds, transmit the catch and DD state over LoRa, then sleep. Wake immediately on a beam-break interrupt to count.
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.
/* βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Smart Pest Trap Counter β ESP32, modulated IR gate, DD model, LoRa
Counts insects falling through a pheromone trap via a modulated
infrared gate with pulse-width discrimination, accumulates
degree-days for emergence prediction, and reports over LoRa on solar.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
#include <WiFi.h>
#include <DHT.h>
#include <LoRa.h>
#include <SPI.h>
#include <Preferences.h>
#include <time.h>
#define PIN_GATE 34 // demodulated detector (LOW = beam broken)
#define PIN_EMIT 25 // modulated emitter drive
#define PIN_DHT 4
#define LORA_CS 5
#define LORA_RST 14
#define LORA_DIO0 2
#define T_MIN_US 2000UL
#define T_MAX_US 40000UL
#define T_BASE 10.0f // degree-day base temperature (species)
#define DD_TARGET 350.0f // DD to next-generation emergence
#define CATCH_ACTION 8 // daily catch action threshold
DHT dht(PIN_DHT, DHT22);
Preferences prefs;
RTC_DATA_ATTR uint32_t dailyCount = 0;
RTC_DATA_ATTR float ddTotal = 0;
RTC_DATA_ATTR float tMax = -99, tMin = 99;
RTC_DATA_ATTR int lastDay = -1;
volatile uint32_t breakStart = 0;
void IRAM_ATTR onEdge() {
uint32_t now = micros();
if (digitalRead(PIN_GATE) == LOW) breakStart = now;
else {
uint32_t dt = now - breakStart;
if (dt > T_MIN_US && dt < T_MAX_US) dailyCount++;
}
}
// Square-wave the emitter so the detector can reject ambient IR.
// (A hardware timer/LEDC does this continuously; shown here conceptually.)
void startBeam() {
ledcSetup(0, 4000, 8); // 4 kHz carrier
ledcAttachPin(PIN_EMIT, 0);
ledcWrite(0, 128); // 50% duty
}
void transmit(float t, float rh, bool block) {
bool action = (dailyCount >= CATCH_ACTION) || (ddTotal >= DD_TARGET);
LoRa.beginPacket();
LoRa.printf("{\"trap\":1,\"catch\":%u,\"dd\":%.0f,\"t\":%.1f,"
"\"rh\":%.0f,\"blocked\":%d,\"action\":%d}",
dailyCount, ddTotal, t, rh, block ? 1 : 0, action ? 1 : 0);
LoRa.endPacket();
}
void setup() {
Serial.begin(115200);
pinMode(PIN_GATE, INPUT_PULLUP);
startBeam();
attachInterrupt(PIN_GATE, onEdge, CHANGE);
dht.begin();
// ββ periodic housekeeping wake ββ
float t = dht.readTemperature();
float rh = dht.readHumidity();
if (!isnan(t)) { if (t > tMax) tMax = t; if (t < tMin) tMin = t; }
struct tm tm; getLocalTime(&tm);
if (tm.tm_yday != lastDay && lastDay >= 0) {
float dd = fmaxf(0, (tMax + tMin) / 2.0f - T_BASE);
ddTotal += dd; // bank yesterday's degree-days
prefs.begin("trap", false);
prefs.putULong("hist", prefs.getULong("hist", 0) + dailyCount);
prefs.putFloat("dd", ddTotal);
prefs.end();
dailyCount = 0; tMax = -99; tMin = 99; // reset for the new day
}
lastDay = tm.tm_yday;
bool blocked = (digitalRead(PIN_GATE) == LOW &&
micros() - breakStart > 2000000UL);
SPI.begin();
LoRa.setPins(LORA_CS, LORA_RST, LORA_DIO0);
LoRa.begin(433E6);
LoRa.setSpreadingFactor(10);
transmit(t, rh, blocked);
// Light sleep keeps the gate interrupt live to count while idle.
esp_sleep_enable_timer_wakeup(900ULL * 1000000ULL); // 15 min housekeeping
esp_sleep_enable_ext0_wakeup((gpio_num_t)PIN_GATE, 0); // wake on beam-break
esp_light_sleep_start();
// execution resumes here after either wake; loop() re-runs the cycle
}
void loop() {
// On wake, do a quick housekeeping pass then sleep again.
float t = dht.readTemperature();
float rh = dht.readHumidity();
if (!isnan(t)) { if (t > tMax) tMax = t; if (t < tMin) tMin = t; }
bool blocked = (digitalRead(PIN_GATE) == LOW &&
micros() - breakStart > 2000000UL);
transmit(t, rh, blocked);
esp_sleep_enable_timer_wakeup(900ULL * 1000000ULL);
esp_sleep_enable_ext0_wakeup((gpio_num_t)PIN_GATE, 0);
esp_light_sleep_start();
}
Configuration & Calibration
Configuration steps
- Set T_BASE, DD_TARGET and CATCH_ACTION to the target pest's biology and the local IPM guidance.
- Tune the beam thresholds and the pulse-width window (T_MIN/T_MAX) to your gate geometry and target insect size.
- Match the emitter modulation frequency between the drive and the detector demodulation.
- Choose the region-legal LoRa frequency and the housekeeping interval.
Calibration procedure
An uncalibrated sensor produces confident, precise, wrong numbers. Do this once per physical unit and record the constants.
Beam baseline
Record the resting demodulated amplitude in full sun and shade; set break/restore thresholds with hysteresis. If the two ambients differ much, the modulation/demodulation needs work.
Pulse-width window
Measure the beam-break duration of real target insects (or a matched proxy) and set T_MIN/T_MAX to bracket them while excluding slow debris.
Count truth-check
Periodically compare the automatic count against a manual tally (and the verification photo) over a few days; adjust the window until they agree.
Network Architecture & Connectivity
Communication protocol
A compact JSON packet β daily catch, degree-day total, temperature and the action flag β goes over LoRa on the housekeeping schedule, with an extra transmit when the action threshold is first crossed so the advice is timely.
| Topic / endpoint | Direction | Payload |
|---|---|---|
ipm/trap/1/catch | node β broker | daily catch, DD, temp, action flag |
ipm/trap/1/fault | node β broker | gate blocked, low battery |
ipm/trap/1/image | node β broker | verification photo reference (optional) |
Message contract between the device and the broker.
Cloud platform configuration
The gateway forwards to an MQTT broker feeding an IPM dashboard that keeps each trap's catch history and overlays them on a farm map, so pest pressure is seen spatially and over time β the basis for area-wide decisions.
Dashboard setup
Per-trap daily-catch bars with the action threshold drawn in, the cumulative degree-day curve toward the emergence target, and a farm map coloured by current pressure.
Mobile app integration
A push alert fires when a trap first crosses its action threshold or reports a blocked gate, naming the trap and its location.
Security considerations
- A per-node key and rolling counter stop a stray node from injecting false catches that could trigger an unnecessary spray.
- Authenticate the broker so only the farm's dashboard reads the pest data.
- Alert on a trap going silent so a dead node is not read as zero pressure.
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 |
|---|---|
| Wave a hand through the beam | Exactly one count registered, in sun and in shade |
| Hold an obstruction in the beam for seconds | No count; "gate blocked" fault raised |
| Flick a fast tiny object through | Sub-threshold glitch ignored (no count) |
| Advance the clock past midnight | Daily count rolls over; degree-days banked; history preserved |
| Drive temperature to accumulate DD past target | Action flag set even before the catch threshold |
| Run a solar day/night cycle | Battery recovers; RTC counters persist across sleeps |
Bench-test checklist. If a row fails, stop and fix it before moving on.
Expected output
The dashboard shows a daily catch bar chart with the action threshold marked, the cumulative degree-day curve against the emergence target, and any "gate blocked" faults.
{
"trap": 1,
"catch": 11,
"dd": 372,
"t": 24.6,
"rh": 55,
"blocked": 0,
"action": 1
}
Here the daily catch of 11 exceeds the action threshold and the degree-day total (372) has passed the emergence target (350) β a strong, well-timed signal to scout and consider treatment.
Troubleshooting: Common Errors & Fixes
Performance Optimisation
- Light-sleep with the gate as a wake source: near-zero current while idle, instant counting on a beam-break.
- Keep the emitter modulation efficient (LEDC hardware) rather than bit-banging it awake.
- Only wake the camera for the occasional verification shot; it dwarfs the counter's power draw.
- Transmit on the housekeeping schedule plus threshold events, not per insect.
- 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. - Batch several samples into one MQTT publish. Radio time, not CPU time, dominates the energy budget.
- Set the MQTT keep-alive to a value that matches your reporting interval so the broker does not churn reconnections.
- 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.
Safety Precautions
- Handle and store pheromone lures carefully; they are potent attractants and cross-contamination ruins selectivity.
- The counter informs spray decisions β it does not replace scouting, resistance management or label compliance.
- Keep the lithium battery and charger sealed against field moisture and dust.
- Site traps and any lure-handling away from where children or livestock could reach them.
- 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
- Replace the pheromone lure on its label interval; a spent lure quietly stops catching.
- Empty the collection cup and clean the gate faces so debris does not fake or block counts.
- Re-truth the count against a manual tally periodically and adjust the window.
- Clean the solar panel and check the antenna after storms.
- 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.
- Keep the broker and dashboard containers patched, and rotate device credentials at least once a year.
- 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.
- Run a small on-device image classifier to verify species automatically from the periodic photo.
- Add a second lure/gate channel to monitor two pests from one node.
- Fuse many traps into a live degree-day + catch model that forecasts the next generation's peak.
- Add wind and a weather feed to flag migratory influx events that a lure-based count would otherwise misattribute.
- 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 over-the-air firmware updates so you never have to physically reach a deployed node again.
- 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.