Contents — 26 sections
Project Overview
Counts products passing on a conveyor accurately and automatically — with the debouncing, gap-detection and edge cases handled so the tally is right, not roughly right.
Counting the items coming off a production line sounds trivial until you try to do it reliably at speed. A break-beam across the belt seems obvious — an item blocks the beam, add one — but a naive counter double-counts a single tall item that flickers the beam, misses two items that touch as they pass, counts a hand reaching in, and drifts off over a shift so the tally no longer matches reality. Getting an accurate automatic count means handling all of those edge cases deliberately. This project builds a conveyor item counter that is right, not roughly right, because in production a count that is a few percent off is a count nobody trusts.
The sensing is a beam-break (an IR emitter/receiver across the belt, or an ultrasonic/photoelectric sensor), but the intelligence is in the signal processing. Each item produces a beam-break of a characteristic duration as it passes at belt speed; the counter debounces the edges so a single item cannot register twice, uses the gap between items to separate two that pass close together, and applies a plausible duration window to reject things that are not products — a hand, a jam, a long shadow. It can infer direction with a second beam so items going backwards (or a rejected item pulled off) are not miscounted. The result is a tally that holds up against a manual count across a full shift.
Beyond the raw count, per-line counting gives production insight: throughput rate (items per minute), which reveals slowdowns and stoppages; totals per shift/batch for accurate production records; and gap/rate patterns that flag jams or feed problems. It reports to a dashboard and can drive downstream actions (batch complete, carton full). It is honest that a simple beam counts objects breaking the beam, not verified good products — quality and identity need a camera or other sensing — and that very fast lines or touching/overlapping items push a single beam to its limits. But as an accurate, well-debounced, edge-case-aware counter, it replaces guesswork and manual tallies with a production count a line can actually rely on.
What this project does
- Counts items passing on a conveyor with a beam-break sensor
- Debounces edges so one item never double-counts
- Uses inter-item gaps to separate items passing close together
- Rejects non-products by a plausible duration window
- Infers direction with a second beam (ignore backward/rejected items)
- Reports throughput rate and per-shift/batch totals
- Flags jams/feed problems from gap and rate patterns
Real-World Applications
| Setting | How it is used |
|---|---|
| Production line counting | Accurate automatic tallies of products coming off a line for records and pay/output tracking. |
| Packing / cartoning | Counting items into a carton and triggering "carton full"/batch-complete actions. |
| Throughput / OEE input | Feeding item rate into productivity and OEE calculations, and flagging slowdowns. |
| Warehouse / sortation | Counting items past a point on a belt for inventory and flow monitoring. |
Deployment contexts where a build of this kind earns its keep.
Features & Capabilities
- Accurate counting with proper debouncing and gap logic
- Duration-window rejection of hands/jams/shadows
- Optional direction detection (two beams)
- Throughput rate and batch/shift totals
- Jam/stoppage detection from rate patterns
- Dashboard reporting and downstream triggers
- Honest scope: counts objects, not verified good product
Difficulty, Time & Required Skills
| Attribute | Value |
|---|---|
| Difficulty level | Beginner |
| Estimated completion time | 8–14 hours |
| Indicative build cost | ₹1,800 – ₹3,200 |
| Primary discipline | Industrial |
| Reference platform | ESP32 DevKit V1 (ESP-WROOM-32) |
Skills you should have (or will pick up)
- Beam-break sensing and clean edge detection
- Debouncing, gap logic and duration-window filtering
- Direction detection with two sensors
- Rate/throughput computation and jam detection
- Reporting counts and triggering downstream actions
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 |
| 0.96″ SSD1306 OLED display Static images burn in — invert or scroll the screen periodically. | 128 × 64 monochrome, 1.3–3.3 V logic, 100 kHz–400 kHz I²C | 1 | ₹250 |
| 5 V single-channel opto-isolated relay module Cut the JD-VCC jumper and feed the coil separately for true isolation. | SPDT contacts rated 10 A @ 250 VAC / 10 A @ 30 VDC, opto-isolated input | 1 | ₹90 |
| Active piezo buzzer 5 V Active buzzers make tone on DC; passive ones need a PWM carrier. | 85 dB at 10 cm, 2.3 kHz resonance, 12 mm diameter | 1 | ₹25 |
| 5 V 3 A regulated SMPS adapter Measure the real output — many "3 A" adapters sag below 4.7 V at 2 A. | 100–240 VAC in, 5 V ±5 % out, 3 A, short-circuit and over-voltage protection | 1 | ₹350 |
| Break-beam sensor pair(s) Two beams a short distance apart give direction | IR emitter/receiver (or photoelectric) across the belt; two for direction | 2 | ₹600 |
| Sensor mounts + shielding | Rigid mounts and hoods to hold aim and reject ambient light | 1 | ₹250 |
| Display / stack-light | Count/rate display and optional stack-light for batch-complete | 1 | ₹500 |
| Enclosure | Industrial housing for the controller near the line | 1 | ₹350 |
Estimated total: ₹2,910, 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 |
| 0.96″ SSD1306 OLED display | 128 × 64 monochrome, 1.3–3.3 V logic, 100 kHz–400 kHz I²C | 3.3–5 V | I²C (0x3C) | Datasheet |
| 5 V single-channel opto-isolated relay module | SPDT contacts rated 10 A @ 250 VAC / 10 A @ 30 VDC, opto-isolated input | 5 V coil | Digital (active-low) | Datasheet |
| Active piezo buzzer 5 V | 85 dB at 10 cm, 2.3 kHz resonance, 12 mm diameter | 3–5 V | Digital / PWM | Datasheet |
| 5 V 3 A regulated SMPS adapter | 100–240 VAC in, 5 V ±5 % out, 3 A, short-circuit and over-voltage protection | 5 V | DC barrel / USB | 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. |
| 0.96″ SSD1306 OLED display | 3.3–5 V | 20 | Static images burn in — invert or scroll the screen periodically. |
| 5 V single-channel opto-isolated relay module | 5 V coil | 70 | Cut the JD-VCC jumper and feed the coil separately for true isolation. |
| Active piezo buzzer 5 V | 3–5 V | 30 | Active buzzers make tone on DC; passive ones need a PWM carrier. |
| 5 V 3 A regulated SMPS adapter | 5 V | 3000 | Measure the real output — many "3 A" adapters sag below 4.7 V at 2 A. |
Summed typical draw is 3300 mA. With a 1.5× design margin the supply should deliver at least 5000 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 |
| PubSubClient 2.8 | Lightweight MQTT 3.1.1 client for constrained devices. | Library Manager → "PubSubClient" by Nick O'Leary |
| Adafruit SSD1306 + GFX 2.5.x | Framebuffer and text/graphics primitives for the OLED. | Library Manager → "Adafruit SSD1306" |
| 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 |
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 |
|---|---|---|---|
| Beam A receiver | OUT | GPIO 34 | Primary beam-break |
| Beam B receiver | OUT | GPIO 35 | Second beam (direction) |
| Reset/batch | btn | GPIO 27 | Reset / batch mark |
| OLED | SDA/SCL | GPIO 21/22 | Count + rate |
| Batch relay/stack-light | IN | GPIO 26 | Batch-complete action |
| Buzzer | IN | GPIO 13 | Jam/batch alert |
| Wi-Fi | on-chip | — | Report counts |
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
- Mount the beam across the belt at a height that every item breaks but the belt/guides do not, and shield the receiver from ambient light (modulated beams help).
- For direction, place two beams a small, known distance apart; the order in which they break gives travel direction.
- Use the sensor's clean digital output (or threshold an analogue one with hysteresis) so edges are crisp.
- Drive a batch relay/stack-light for downstream actions and a buzzer for jam/batch alerts.
- Keep wiring away from motor/VFD noise that can cause false edges.
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 gap between a naive beam counter and an accurate one is entirely in signal processing, because the raw beam signal is deceptively messy. A single item passing does not produce one clean edge pair; it can produce edge chatter as it enters and leaves the beam, a flicker if it has a hole or a shiny spot, and its duration depends on its length and the belt speed. Meanwhile two items touching produce one long break that should count as two, a hand reaching in produces a very long break that should count as none, and electrical noise produces spurious edges. Accurate counting means turning this messy reality into a correct tally through a handful of well-chosen rules.
The first rule is debouncing. After a valid count, the counter ignores further edges for a short refractory time (or requires the beam to clear and re-break) so the entry/exit chatter of one item cannot register as several. This single measure eliminates the most common error — the same item counted two or three times — and it is tuned so the refractory time is shorter than the minimum realistic gap between items but long enough to swallow edge chatter.
The second and third rules handle the harder edge cases. A duration window rejects breaks that are implausibly long or short: an item at belt speed breaks the beam for a characteristic range of times, so a very long break (a hand, a jam, a stalled item) or a very short one (noise) is not counted, and a persistent long break can be flagged as a jam. And separating touching items — two products with no gap between them that make one continuous break — is where a second beam or a length model helps: if the break is about twice the single-item duration, it is two items; and with two beams a known distance apart, the timing between them gives both the count and the belt speed, disambiguating touching items and giving direction so a backward-moving or rejected item is not miscounted.
On top of the correct count, the counter derives production value: the throughput rate (items per minute) exposes slowdowns and stoppages in real time; batch/shift totals give trustworthy production records and can trigger downstream actions (carton full, batch complete); and patterns in the gaps and rate flag jams and feed problems before they waste much product. The design is candid about what a beam does and does not know: it counts objects that break the beam, not verified good products — it cannot tell a good unit from a defective one, or item A from item B, which needs a camera or other identity/quality sensing — and very fast lines with tiny gaps push a single beam to its limit. But engineered with proper debouncing, a duration window, gap/direction logic and validation against a manual count, it delivers what production actually needs: a count that is right, holding up over a full shift, rather than a rough guess that no one trusts.
The maths behind it
Debounce / refractory
After a valid count, ignore edges for t_refractory:
count only if (t_now − t_last_count) > t_refractory
and require the beam to have cleared (rising edge) first
t_refractory < min realistic item gap, > edge chatter.
Prevents one item counting multiple times.
Duration window + jam
Item break duration at belt speed v, item length L:
t_break ≈ (L + beam_width) / v
count if t_min < t_break < t_max
t_break > t_jam (>> t_max) → JAM / stalled item → alert
~2·t_single with no gap → two touching items
Rejects hands/noise; separates touching items.
Direction + throughput
Two beams distance s apart; A breaks then B → forward:
v = s / (t_B − t_A), direction = sign(t_B − t_A)
count only forward-moving items
rate = items / elapsed_time (items per minute)
falling rate / long gaps → slowdown/stoppage.
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.
Mount and shield the beam
Fit the emitter/receiver across the belt at a height every item breaks but the belt and guides do not, shielded from ambient light (a modulated beam is ideal). For direction, add a second beam a known short distance downstream.
Wire the controller and outputs
Connect the beam outputs to the ESP32, plus a display for count/rate, a batch relay/stack-light for downstream actions and a buzzer for jam/batch alerts.
Set up reporting
Configure Wi-Fi reporting of counts/rate to a dashboard and the batch/shift logic.
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.
Count accurately with debounce, window and direction
On each beam clear-and-break, measure the break duration, apply the duration window and refractory time, check direction with the second beam, and increment only for a valid forward item.
cppcounter.ino#define T_MIN_MS 20 // shortest plausible item break #define T_MAX_MS 400 // longest single-item break #define T_JAM_MS 2000 // beyond this = jam/stalled #define T_REFRACTORY 15 // min ms between counts uint32_t breakStart=0, lastCount=0, aBreak=0, bBreak=0; long count=0; // Primary beam edge (LOW = broken). Returns true if it counted. bool onBeamA(bool broken, uint32_t now){ if (broken) { breakStart = now; aBreak = now; return false; } // beam cleared -> evaluate the completed break uint32_t dur = now - breakStart; if (dur > T_JAM_MS) { raiseJam(); return false; } if (dur < T_MIN_MS || dur > T_MAX_MS) return false; // reject non-item if (now - lastCount < T_REFRACTORY) return false; // debounce bool forward = (bBreak >= aBreak) && (bBreak - aBreak < 500); // A before B if (!forward) return false; // ignore backward count++; lastCount = now; // roughly-2x single duration with no gap could be two touching items: if (dur > 1.7f*T_TYPICAL) count++; // count the pair return true; }if (dur > T_JAM_MS) { raiseJam(); return false; }A break far longer than any item means a jam or a stalled product, so it is alarmed rather than counted.if (dur < T_MIN_MS || dur > T_MAX_MS) return falseThe duration window rejects noise (too short) and hands/obstructions (too long), counting only breaks consistent with a passing item.if (now - lastCount < T_REFRACTORY) return falseThe refractory time debounces edge chatter so one item cannot register as several.bool forward = (bBreak >= aBreak)The order in which the two beams broke gives travel direction, so backward-moving or rejected items are not counted.if (dur > 1.7f*T_TYPICAL) count++A break near twice the typical single-item duration with no gap is two touching items, so the pair is counted correctly.Compute rate, detect jams, report
Maintain items-per-minute and batch/shift totals, flag jams/slowdowns from the rate and gap patterns, trigger batch-complete actions, and report to the dashboard.
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.
/* ═══════════════════════════════════════════════════════════════
Conveyor Item Counter — ESP32, debounced beam-break
Counts items on a belt accurately with debouncing, a duration window,
optional two-beam direction, and touching-item separation. Computes
throughput and batch totals, flags jams, and triggers batch actions.
══════════════════════════════════════════════════════════════════ */
#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include <Preferences.h>
#define PIN_A 34 // primary beam receiver (LOW = broken)
#define PIN_B 35 // second beam (direction)
#define PIN_BATCH_OUT 26
#define PIN_BUZZER 13
#define T_MIN_MS 20
#define T_MAX_MS 400
#define T_JAM_MS 2000
#define T_REFRACTORY 15
#define T_TYPICAL 120
#define BATCH_SIZE 24
Adafruit_SSD1306 oled(128,64,&Wire);
Preferences prefs;
WiFiClient net; PubSubClient mqtt(net);
volatile uint32_t breakStart=0, aBreak=0, bBreak=0, lastCount=0;
volatile long count=0, batchCount=0;
uint32_t rateWindowStart=0; long rateWindowCount=0; float itemsPerMin=0;
bool aBroken=false;
void IRAM_ATTR onA() { // both edges of beam A
uint32_t now = millis();
if (digitalRead(PIN_A)==LOW) { breakStart=now; aBreak=now; aBroken=true; return; }
aBroken=false;
uint32_t dur = now - breakStart;
if (dur > T_JAM_MS) return; // jam handled in loop
if (dur < T_MIN_MS || dur > T_MAX_MS) return;
if (now - lastCount < T_REFRACTORY) return;
bool forward = (bBreak >= aBreak) && (bBreak - aBreak < 500);
if (!forward) return;
count++; batchCount++; lastCount = now; rateWindowCount++;
if (dur > 1.7f*T_TYPICAL) { count++; batchCount++; rateWindowCount++; }
}
void IRAM_ATTR onB() { if (digitalRead(PIN_B)==LOW) bBreak=millis(); }
void setup(){
Serial.begin(115200);
pinMode(PIN_A, INPUT_PULLUP); pinMode(PIN_B, INPUT_PULLUP);
pinMode(PIN_BATCH_OUT, OUTPUT); pinMode(PIN_BUZZER, OUTPUT);
attachInterrupt(PIN_A, onA, CHANGE);
attachInterrupt(PIN_B, onB, CHANGE);
Wire.begin(21,22); oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);
WiFi.begin(WIFI_SSID, WIFI_PASS); mqtt.setServer(MQTT_HOST,1883);
prefs.begin("cnt",true); count=prefs.getLong("count",0); prefs.end();
rateWindowStart = millis();
}
void loop(){
if(!mqtt.connected()&&WiFi.status()==WL_CONNECTED) mqtt.connect("cnt-1");
mqtt.loop();
uint32_t now = millis();
// throughput rate over a rolling 60 s window
if (now - rateWindowStart >= 60000) {
itemsPerMin = rateWindowCount;
rateWindowCount = 0; rateWindowStart = now;
}
// jam detection: beam A held broken too long
if (aBroken && now - breakStart > T_JAM_MS) {
digitalWrite(PIN_BUZZER, HIGH);
mqtt.publish("line/1/alert", "JAM (beam held)");
} else digitalWrite(PIN_BUZZER, LOW);
// batch complete
if (batchCount >= BATCH_SIZE) {
digitalWrite(PIN_BATCH_OUT, HIGH); delay(200);
digitalWrite(PIN_BATCH_OUT, LOW);
mqtt.publish("line/1/batch", "complete");
batchCount = 0;
}
oled.clearDisplay(); oled.setCursor(0,0);
oled.printf("Count: %ld\nBatch: %ld/%d\n%.0f /min",
count, batchCount, BATCH_SIZE, itemsPerMin);
oled.display();
char m[120];
snprintf(m,sizeof m,"{\"count\":%ld,\"rate\":%.0f}", count, itemsPerMin);
mqtt.publish("line/1/count", m);
prefs.begin("cnt",false); prefs.putLong("count",count); prefs.end();
delay(500);
}
Configuration & Calibration
Configuration steps
- Set the duration window, refractory time and typical single-item duration for your product and belt speed.
- Configure the two-beam spacing for direction/speed, the batch size, and jam threshold.
- Set reporting (dashboard) and downstream actions (batch relay/stack-light).
- Enable ambient-light shielding/modulation appropriate to the environment.
Calibration procedure
An uncalibrated sensor produces confident, precise, wrong numbers. Do this once per physical unit and record the constants.
Window/refractory
Run known items at production speed and set the duration window and refractory so every item counts once and none is missed.
Touching items
Test with items deliberately touching; tune the double-count threshold (or rely on the second beam) so pairs count correctly.
Manual validation
Compare the automatic count to a manual count over a run; adjust until they agree, then across a full shift.
Network Architecture & Connectivity
Communication protocol
Counts and rate publish on a slow cadence; jams and batch-complete publish immediately. The count persists locally so it is not lost on a reset.
| Topic / endpoint | Direction | Payload |
|---|---|---|
line/1/count | node → dashboard | running count, rate, batch progress |
line/1/alert | node → ops | jam / slowdown |
line/1/batch | node → line | batch complete (trigger action) |
Message contract between the device and the broker.
Cloud platform configuration
A production dashboard trends throughput and totals per line/shift/batch, feeds OEE, and surfaces jams and slowdowns.
Dashboard setup
Live count and rate per line, shift/batch totals, and jam/slowdown markers.
Mobile app integration
Alerts for jams, slowdowns and batch-complete.
Security considerations
- Authenticate nodes so counts cannot be spoofed.
- Persist counts locally so a network/power blip does not lose the tally.
- Alert on counter silence so a down line is noticed.
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 |
|---|---|
| Pass single items at speed | Each counts exactly once (debounced) |
| Pass two touching items | Counted as two (double-duration or second-beam logic) |
| Reach a hand into the beam | Long break rejected (not counted) |
| Stall an item in the beam | Jam alert; not counted |
| Move an item backward (two beams) | Not counted (direction) |
| Validate over a shift | Automatic total matches the manual count |
Bench-test checklist. If a row fails, stop and fix it before moving on.
Expected output
The display and dashboard show the running count, batch progress and throughput rate; jams and batch-complete raise alerts/actions.
{
"line": 1,
"count": 4821,
"rate": 96,
"batch": "18/24"
}
A live tally with throughput (96/min) and batch progress; a jam would raise an alert and a completed batch would trigger the downstream action — an accurate, actionable production count rather than a rough estimate.
Troubleshooting: Common Errors & Fixes
Performance Optimisation
- Handle beam edges in interrupts for accurate timing at speed.
- Keep the debounce/window logic lightweight so it runs in the ISR without missing edges.
- Compute rate over a rolling window; report counts on a slow cadence.
- Persist the count so resets do not lose the shift total.
- 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
- Mount sensors clear of moving belt parts and pinch points; follow machine-guarding rules.
- This counts objects breaking the beam, not verified good product — do not treat it as quality/identity verification.
- Validate against a manual count before relying on the tally for records or pay.
- Keep wiring away from motor/VFD noise and follow electrical safety near the line.
- Mains voltage kills. Anything on the load side of the relay is at 230 V. Do not work on a powered circuit, and never leave exposed mains wiring on a bench where someone could touch it.
- Keep at least 6 mm of creepage between the mains and low-voltage sides of any board you make, and never route mains tracks under the microcontroller.
- Have a qualified electrician do the final installation into a consumer unit or wall fitting. In most jurisdictions this is a legal requirement, not a suggestion.
- Fit an RCD/RCBO upstream and fuse the load appropriately for its rating.
- 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
- Clean the beam optics; dust/product buildup causes false or missed counts.
- Re-validate against a manual count periodically and after product/speed changes.
- Check sensor alignment and shielding remain good.
- Verify batch/jam actions and the persisted count.
- Re-check every screw terminal and header after the first week — thermal cycling loosens connections that felt tight on day one.
- 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.
- Add a camera for product identity/quality alongside the count.
- Add speed-aware length measurement to classify item sizes.
- Feed counts directly into OEE and MES systems.
- Add reject verification (count items removed) for accurate good-count.
- 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.
- Photoelectric / break-beam sensorsReference
- Debouncing and edge detectionReference
- Conveyor systems and countingReference
- Throughput and OEEReference
- Direction sensing with dual beamsReference