Siddhant Kumar
Project 061 · Industrial

Conveyor Item Counter.

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.

Beginner 8–14 hours 31 min read SensorCountingIndustry
Jump to source Bill of materials
Conveyor Item Counter — reference build illustration MCU VCC · GND · SIG · NC
Difficulty
Beginner
Build time
8–14 hours
Indicative cost
₹1,800 – ₹3,200
Platform
ESP32 DevKit V1 (ESP-WROOM-32)
Category
Industrial
Last updated
28 July 2026
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.

Automated machinery on a factory production line
A beam across the belt counts products passing on the line. Photograph sourced from Wikimedia Commons — Factory automation.jpg. Reused under the licence stated on that page; please check it before republishing.

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

SettingHow it is used
Production line countingAccurate automatic tallies of products coming off a line for records and pay/output tracking.
Packing / cartoningCounting items into a carton and triggering "carton full"/batch-complete actions.
Throughput / OEE inputFeeding item rate into productivity and OEE calculations, and flagging slowdowns.
Warehouse / sortationCounting 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

AttributeValue
Difficulty levelBeginner
Estimated completion time8–14 hours
Indicative build cost₹1,800 – ₹3,200
Primary disciplineIndustrial
Reference platformESP32 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.

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
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 out1₹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²C1₹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 input1₹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 diameter1₹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 protection1₹350
Break-beam sensor pair(s)
Two beams a short distance apart give direction
IR emitter/receiver (or photoelectric) across the belt; two for direction2₹600
Sensor mounts + shieldingRigid mounts and hoods to hold aim and reject ambient light1₹250
Display / stack-lightCount/rate display and optional stack-light for batch-complete1₹500
EnclosureIndustrial housing for the controller near the line1₹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

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
TCRT5000 IR reflectance sensor module1–8 mm optimum sensing gap, 950 nm emitter, analogue + digital out3.3–5 VAnalogue + digitalDatasheet
0.96″ SSD1306 OLED display128 × 64 monochrome, 1.3–3.3 V logic, 100 kHz–400 kHz I²C3.3–5 VI²C (0x3C)Datasheet
5 V single-channel opto-isolated relay moduleSPDT contacts rated 10 A @ 250 VAC / 10 A @ 30 VDC, opto-isolated input5 V coilDigital (active-low)Datasheet
Active piezo buzzer 5 V85 dB at 10 cm, 2.3 kHz resonance, 12 mm diameter3–5 VDigital / PWMDatasheet
5 V 3 A regulated SMPS adapter100–240 VAC in, 5 V ±5 % out, 3 A, short-circuit and over-voltage protection5 VDC barrel / USBDatasheet

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.
TCRT5000 IR reflectance sensor module3.3–5 V20Ambient sunlight saturates it — shroud the sensor on outdoor robots.
0.96″ SSD1306 OLED display3.3–5 V20Static images burn in — invert or scroll the screen periodically.
5 V single-channel opto-isolated relay module5 V coil70Cut the JD-VCC jumper and feed the coil separately for true isolation.
Active piezo buzzer 5 V3–5 V30Active buzzers make tone on DC; passive ones need a PWM carrier.
5 V 3 A regulated SMPS adapter5 V3000Measure 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.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
WiFi (ESP32 core) bundledStation/AP connection management for the ESP32.Bundled with the ESP32 Arduino core
PubSubClient 2.8Lightweight MQTT 3.1.1 client for constrained devices.Library Manager → "PubSubClient" by Nick O'Leary
Adafruit SSD1306 + GFX 2.5.xFramebuffer and text/graphics primitives for the OLED.Library Manager → "Adafruit SSD1306"
ArduinoJson 7.xZero-allocation JSON serialisation and parsing.Library Manager → "ArduinoJson" by Benoit Blanchon
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.

Conveyor Item Counter — system block diagramFunctional block diagram of the Conveyor Item Counter system. SenseBeam A/Bbreak + directionProcessESP32debounce + gap + windowDirectionA→B vs B→ACountTally + rateshift/batchJam detectrate patternAct/reportBatch actioncarton/lightDashboardthroughputrightrightnone
Conveyor Item Counter — 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.

Conveyor Item Counter — wiring schematicConnection schematic showing which controller pin drives each peripheral. Sensors / InputsControllerActuators / OutputsESP32 DevKit V1(ESP-WROOM-32)3.3 V logic / 5 V USBBeam A receiverGPIO 34Primary beam-breakBeam B receiverGPIO 35Second beam(direction)Reset/batchGPIO 27Reset / batch markOLEDGPIO 21/22Count + rateBatch relay/stack-lightGPIO 26Batch-completeactionBuzzerGPIO 13Jam/batch alertWi-FiReport counts
Conveyor Item Counter — wiring schematic
PeripheralPeripheral pinController pinSignal
Beam A receiverOUTGPIO 34Primary beam-break
Beam B receiverOUTGPIO 35Second beam (direction)
Reset/batchbtnGPIO 27Reset / batch mark
OLEDSDA/SCLGPIO 21/22Count + rate
Batch relay/stack-lightINGPIO 26Batch-complete action
BuzzerINGPIO 13Jam/batch alert
Wi-Fion-chipReport 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.
An ESP32 development board with the ESP-WROOM-32 module and USB connector
ESP32 module debouncing edges and applying gap/duration/direction logic for an accurate tally. 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.

Conveyor Item Counter — architecture stackLayered architecture from hardware to user interface. Hardware layerESP32 DevKit V1 (ESP-WROOM-32) · TCRT5000 IR reflectance sensor moduleDriver layerwifi · pubsub · ssd1306 · arduinojsonApplication logicsampling loop · filtering · thresholds · state machineTransport layerWi-Fi + MQTT → production dashboard · TLS · retry and backoffPresentation layerdashboard · mobile notifications · historical charts
Conveyor Item Counter — architecture stack

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

plainDebounce / 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

plainDuration 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

plainDirection + 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.

Conveyor Item Counter — firmware flowchartControl flow through the main program loop. Beam edgeBreak duration in itemwindow?Direction forward?Reject (hand/jam/noise)Direction forward?Count +1 (after gap/debounce)Ignore/decrementCount +1 (after gap/debounce)Reject (hand/jam/noise)Ignore/decrementUpdate rate; batch/jam checksReport; trigger actions
Conveyor Item Counter — 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.

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

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

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

  1. 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.
  2. 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.

cppconveyor-item-counter.ino
/* ═══════════════════════════════════════════════════════════════
   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);
}
void IRAM_ATTR onA()The primary beam is handled in an interrupt on both edges, measuring each break's duration and applying the window, refractory and direction checks so the count stays accurate at speed.
if (dur > 1.7f*T_TYPICAL) { count++;A break near twice the typical single-item duration is treated as two touching items, so items with no gap between them are still counted correctly.
if (aBroken && now - breakStart > T_JAM_MS)A beam held broken far too long is a jam, which sounds the buzzer and alerts rather than corrupting the count.
if (batchCount >= BATCH_SIZE)Reaching the batch size triggers a downstream action (relay/stack-light) and a batch-complete message — the counter driving the line, not just tallying.
prefs.putLong("count",count)The running total is persisted so a reset or power blip does not lose the shift's count.

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.

  1. Window/refractory

    Run known items at production speed and set the duration window and refractory so every item counts once and none is missed.

  2. Touching items

    Test with items deliberately touching; tune the double-count threshold (or rely on the second beam) so pairs count correctly.

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

Conveyor Item Counter — network topologyPath taken by telemetry from field node to end user. Edge nodesGatewayCloudClientsCounterESP32Other linesper-lineWi-Fi 2.4 GHzRouterto MQTTMQTT 1883Production dashboardcounts + rateDashboardthroughputOpsjam/batch alerts
Conveyor Item Counter — network topology

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 / endpointDirectionPayload
line/1/countnode → dashboardrunning count, rate, batch progress
line/1/alertnode → opsjam / slowdown
line/1/batchnode → linebatch 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.

TestWhat you should see
Pass single items at speedEach counts exactly once (debounced)
Pass two touching itemsCounted as two (double-duration or second-beam logic)
Reach a hand into the beamLong break rejected (not counted)
Stall an item in the beamJam alert; not counted
Move an item backward (two beams)Not counted (direction)
Validate over a shiftAutomatic 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.

jsoncount.json
{
  "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.

An HC-SR04 ultrasonic distance sensor with its two transducers
Throughput rate and batch totals turn the count into actionable production data. Photograph sourced from Wikimedia Commons — HC-SR04.jpg. Reused under the licence stated on that page; please check it before republishing.

Troubleshooting: Common Errors & Fixes

Over-counting

Likely cause. Edge chatter / no debounce

Fix. Add refractory time; require beam clear before next count; shield from noise/ambient light

Missing items

Likely cause. Window too tight or touching items merged

Fix. Widen the window to real break durations; use double-duration/second-beam logic for touching items

Counts hands/jams

Likely cause. No duration window

Fix. Reject too-long breaks; alarm jams

Backward items counted

Likely cause. Single beam, no direction

Fix. Add a second beam and count only forward travel

Drifts vs manual count

Likely cause. Untuned parameters

Fix. Validate against a manual count and tune window/refractory; re-check across a shift

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.

Wi-Fi connects but MQTT never does (state -2)

Likely cause. Wrong broker address or port, a firewall in the way, or the broker requiring credentials the sketch is not sending.

Fix. Test from a laptop on the same network first: mosquitto_sub -h <broker> -t "#" -v. If that works, the problem is on the device — check the IP literal, port 1883 (or 8883 for TLS), and that client.setServer() runs before connect(). PubSubClient state codes are documented in its header.

Readings arrive for a while and then stop

Likely cause. The Wi-Fi or MQTT session dropped and the sketch never reconnects, or the broker dropped the client on keep-alive timeout.

Fix. Never assume the link stays up. Check WiFi.status() and client.connected() at the top of every loop and reconnect with exponential backoff. Add a watchdog so a wedged network stack reboots the device instead of going silent.

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

Why is a simple beam counter inaccurate?

Because raw edges are messy — one item can chatter or flicker the beam, two touching items make one break, and hands/jams break it too. Accurate counting needs debouncing, a duration window and gap/direction logic.

How does it separate two touching items?

A break near twice the single-item duration is treated as two, and with two beams a known distance apart the timing gives the belt speed and disambiguates them — plus direction so backward items are not counted.

Does it count only good products?

No — it counts objects that break the beam. It cannot tell a good unit from a defective one or item A from item B; that needs a camera or other identity/quality sensing.

Why validate against a manual count?

Because a production count nobody trusts is worthless. Comparing to a manual count over a run (and a shift) proves the tuning is right before you rely on the tally.

What about very fast lines?

Very fast lines with tiny gaps push a single beam to its limits. A second beam, faster sensing, or a camera helps; the design is honest about that boundary.

References & Learning Resources

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

  1. Photoelectric / break-beam sensorsReference
  2. Debouncing and edge detectionReference
  3. Conveyor systems and countingReference
  4. Throughput and OEEReference
  5. Direction sensing with dual beamsReference