pawin.dev / OpenHome / Presence Plate
← Back to portfolio

OpenHome Foundation · Open-source hardware

Presence Plate

A desk plate that knows whether your thing is on it.

Designed by Pawin Jirasuntornsiri · Rev A · Firmware MIT · Hardware CC BY-SA 4.0

Ø90 × 24 mm
ESP32-C3 + HX711
15 × WS2812B
90-minute build
Assembled Presence Plate with its blue LED ring lit, sitting on a desk
Photo of the actual first prototype, powered on with its blue ring lit, on a kitchen counter
Not a render

This is the real first prototype

Everything above this line is a CAD render for clarity — this is the actual unit, built from this exact guide, powered on with the ring lit. Top plate is a bare print here, pre-finishing.

How it knows

The top plate rests on the free end of a load cell and touches nothing else. Anything put on the plate bends the cell, and the HX711 reads that bend ten times a second.

It does not compare that reading against a fixed threshold. Load cells drift with room temperature, sometimes by more than a small object weighs, so a threshold eventually lies. Instead the firmware watches for the sudden change of something landing or being lifted. Drift is slow. A placement is fast. That is the whole trick.

Cutaway showing the load cell spanning the case with the plate bolted to its free end
Section. Plate on the load cell's free end, right. Fixed end bolted to the base, left.

What's in the box

Two printed parts, a load cell, an ESP32-C3, an HX711 and 15 LEDs. Nothing exotic — every part is generic and widely stocked, aside from the load cell and the heat-set inserts, which are worth being fussy about (see Parts).

Exploded view showing top plate, load cell, case and base screws
Exploded assembly.

The build

Nine steps, in order

About 90 minutes plus print time. Do them in order — step 7 is a test that has to happen before the case is closed.

Total: 91 minutes + print time
1Tape the wall5 min
  • Line the inside of the case wall with black electrical tape, floor to rim.
  • One continuous band. Overlap the ends 10 mm.
  • Press flat. No bubbles where the strip will sit.
Empty case interior showing the ESP pocket and the load cell mounting pad
Case, open side up. ESP pocket at the back, load cell pad at the front.
2Fit the LED ring10 min
  1. Cut the strip to 15 pixels, on a pad line.
  2. Solder the three wires to the DIN end before fitting.
  3. Fit the 470 Ω resistor in the data wire, at the strip end.
  4. Stick the strip around the inside of the wall, over the tape, LEDs facing out.
  5. Start and finish next to the ESP pocket.
Check

DIN is the end the printed arrows point away from. Wrong end and the strip stays dark.

Keep the strip upright against the wall. Do not curl it flat — that cracks the traces.

3Set the inserts10 min

Two holes in the boss on the plate underside. Ø3.5 mm, 3.0 mm deep, 15 mm apart.

  1. Set the iron to 220 °C for PLA, 240 °C for PETG.
  2. Sit the insert on the hole.
  3. Put the iron in it. Let its own weight take it down. Do not push.
  4. Stop when the flange is flush with the boss face.
  5. Check flush with a straight edge across the boss while still warm.
Underside of the top plate showing the raised boss and two mounting holes
Plate underside, before.
The same boss with two brass heat-set inserts fitted flush
Inserts flush with the boss face.
4Bolt the load cell to the plate5 min

Two M3 × 16 screws, up through the load cell into the inserts.

⚠ The end you bolt here must be the free end

Marked cells: the arrow points to the free end.

Unmarked: hold one end, press the other. The end that deflects is the free end.

Backwards and the plate reads almost nothing.

  • Tighten firm. Do not overtighten — brass in plastic.
  • If the cell's holes are M4 or M5, add an M3 washer under each screw head.
Load cell bolted to the underside of the top plate
Free end bolted to the plate boss.
5Fit the ESP5 min
  1. Dry fit the SuperMini in the pocket. Check the USB-C port lines up with the wall slot.
  2. Superglue at each corner, or a square of VHB.
  3. Let it set before soldering to it.
ESP32-C3 SuperMini seated in its pocket with the USB-C port at the wall slot
SuperMini seated, USB-C at the wall slot.
6Wiring30 min

Full diagram and connection table in Wiring below.

⚠ Three that will cost you the evening
  • HX711 VCC to 3V3, never 5 V. At 5 V its DOUT drives 5 V into a 3.3 V GPIO.
  • Star ground. Every GND wire ends at one physical joint. No daisy chaining.
  • 10 µF and 100 nF at the HX711's own pins. Not on the perfboard.

Placement

  • HX711 in the empty half of the case, 30–50 mm from the load cell.
  • Service loop in the cell's four wires. Anchor them on the fixed end side only.
  • Keep the cell wires clear of the strip's power and data. Cross at 90°.
  • Nothing may touch the top plate or the free end of the cell.
Load cell and ESP installed in the case with the top plate removed
Load cell and ESP fitted. HX711 goes in the empty half.
7Flash and test20 min
Stop — do not fit the base screws yet

Test with the case open. Everything is reachable now and will not be afterwards.

Put secrets.yaml beside the config:

wifi_ssid: "your-network"
wifi_password: "your-password"

Then:

esphome run presence-plate.yaml

Check in order

  1. Boots and joins WiFi. Ring runs a slow blue orbit, then one green flash.
  2. Colours correct. Green flash comes out red → set rgb_order: RGB, reflash.
  3. Pixel 1 correct. First pixel wrong or flickering, rest fine → fit one 1N4007 in series with the strip's +5 V. Silicon, not Schottky.
  4. Cell responds. Watch Load cell raw. Press the plate: the number must move by thousands and return.
  5. Ring goes red, slow orbit. Normal. Means not yet calibrated.

Faults

SymptomCause
Load cell raw reads NaNDT and SCK swapped, or HX711 not powered
Load cell raw steady, never changesCell not in the load path, or the fixed end was bolted to the plate
Load cell raw noisy by thousands at rest100 nF not at the HX711 pins, or cell wires running near the strip
Board resets when the ring lights1000 µF missing or too far from the strip
Strip completely darkDIN connected at the wrong end of the strip
First pixel wrong colour, rest fine3.3 V data level. Fit a 1N4007 in series with the strip +5 V.
Red and green swappedSet rgb_order: RGB in the config and reflash
WiFi drops with 'Auth Expired'power_save_mode: none and output_power: 15dB missing from the config
Plate reads nothing when pressedPlate is touching the case wall. Sand the plate edge.
8Fit the base screws5 min
  1. Two M4 × 16 socket cap screws, up through the counterbores in the underside, into the load cell's fixed end.
  2. Tighten fully. This joint must not move.
  3. Fit three silicone feet, evenly spaced.
⚠ Check the plate floats

Gap ≈1 mm, even all the way round.

Press the rim at three points. It must return each time.

Any contact with the wall and the cell reads nothing. Sand the plate edge. Do not loosen anything.

Underside of the case showing the two counterbored base screws
Underside. M4 heads recess into the counterbores.
9Calibrate1 min

Start it one of two ways:

  • Press Calibrate in Home Assistant, or
  • Knock · · — — · on the plate — short, long, long, short. Tap slowly: 0.5 s, 1 s, 1 s, 0.5 s. The ring fills red. Tap once more to confirm.

Full colour table in Calibration below.

Remember two colours

Blue = put it on. Amber = take it off. The Tare button is not needed.

Step 1 of 9

Wiring

One diagram, one table

Wiring diagram showing the load cell, HX711, ESP32-C3 and LED strip connections
A filled dot is a junction — those wires are connected. A small hop where two lines cross means they are not connected. Click to open the print PDF.
GroupFromToNet
Load cell → HX711REDE+load cell bridge
Load cell → HX711BLACKE−load cell bridge
Load cell → HX711WHITEA−load cell bridge
Load cell → HX711GREENA+load cell bridge
Load cell → HX711B+ and B−not connected
HX711 → ESP32-C3VCC3V3 (never 5 V)+3.3 V
HX711 → ESP32-C3DTGPIO43.3 V logic
HX711 → ESP32-C3SCKGPIO53.3 V logic
HX711 → ESP32-C3GNDStar groundGND
LED ringESP GPIO6 → 470 ΩStrip DIN3.3 V data
LED ringESP 5VStrip 5V+5 V
LED ringStrip GNDStar groundGND
Capacitors1000 µFAcross strip 5V / GND, at the strip+5 V
Capacitors10 µF + 100 nFAcross HX711 VCC / GND, at the pins+3.3 V
⚠ Three that will cost you the evening
  • HX711 VCC to 3V3, never 5 V. At 5 V its DOUT drives 5 V into a 3.3 V GPIO.
  • Star ground. Every GND wire ends at one physical joint. No daisy chaining.
  • 10 µF and 100 nF at the HX711's own pins. Not on the perfboard.

If the LED strip misbehaves

The strip runs on 5 V and wants a data signal of at least 3.5 V. The ESP32-C3 puts out 3.3 V. Most strips accept it. If the first pixel is the wrong colour or flickers while the rest are fine, fit one 1N4007 diode in series with the strip's +5 V. That drops the strip to about 4.3 V and brings the threshold down to 3.0 V. Use a silicon diode, not a Schottky.

Parts

What you need

Everything below is generic and widely available. The only two things worth being fussy about are the load cell and the heat-set inserts.

⚠ Insert length — check before ordering

Plate holes are Ø3.5 × 3.0 mm deep. Use 3 mm inserts.

4 mm inserts bottom out 1 mm proud and stop the load cell seating flat. If you already have 4 mm, drill the holes to 4.5 mm with a 3.5 mm bit first.

The STEP file shows Insert M3 D5 L5mm — a library placeholder. D5 does not fit a Ø3.5 hole.

⚠ The load cell must be 4-wire

Count the wires in the listing photo. Four wires is a full bridge and works. Three-wire pads, sold for bathroom-scale repair, are half bridges and will not work.

Printed

PartQtySpecNote
Case1Ø90 × 24 mmOpen side up. No supports. White filament. 3 perimeters.
Top plate1Ø85 × 3 mm, 2 mm bossBoss down, flat on the bed. Any colour.

Electronics

PartQtySpecNote
ESP32-C3 SuperMini1The pocket is cut for this board
HX711 module1Green breakoutNot in the CAD. Mounts in the empty half.
Bar load cell180 mm, 15 mm hole spacingMUST be 4-wire full bridge.
WS2812B strip, 60 LED/m15 px≈250 mm
470 Ω resistor1Series with DIN, at the strip end
1000 µF 16 V electrolytic1At the strip's 5 V/GND
10 µF electrolytic1At the HX711 pins
100 nF ceramic1At the HX711 pins
1N4007 diode1Spare. Fit only if pixel 1 misbehaves.
5 V 2 A supply1≥2 A on the label
USB-C cable1DATA capableTest it on a phone first
Hookup wire22 AWG strip power, 24–26 AWG elsewhere

Hardware

PartQtySpecNote
M3 heat-set insert23 mm longTop plate. NOT 4 mm — see errata.
M3 × 16 screw2Load cell free end → plate
M4 × 16 socket cap screw2Base → load cell fixed end
Black electrical tapeCase wall
Superglue or VHB tapeESP
Silicone feet3Underside. Three, not four.

Tools

ToolQtySpec
Soldering iron1Heat-set tip or conical tip
Multimeter1Continuity and DC volts
Hex keys1 set2 mm, 2.5 mm
Flush cutters, wire strippers1 set
3.5 mm drill bit1Only if deepening the insert holes

Printing

Before you start

  1. Meter the load cell. Red↔Black ≈ 1 kΩ. White↔Green ≈ 1 kΩ. Every wire to every other: finite, never open.
  2. Plug the bare ESP into USB. Confirm it enumerates.
  3. Drop the top plate into the case. It must clear the wall all round.

Firmware

Configure and flash

The device runs ESPHome. All its behaviour is in one text file, presence-plate.yaml. Four settings at the top are the only ones most people change.

device_name:     "presence-plate" # hostname and entity prefix
object_name:     "object"         # appears in entity names
num_leds:        "15"               # pixels on your ring
cell_rating_g:   "5000"             # the number printed on your load cell

Everything else has a working default.

Flashing

esphome run presence-plate.yaml

First time is over USB. After that it updates over WiFi. You need a secrets.yaml beside it with wifi_ssid and wifi_password.

Home Assistant entities

binary_sensor.<name>_<object>_on_plate     occupancy — the main one
light.<name>_<object>_ring                 the ring, with effects
sensor.<name>_plate_weight                 grams
sensor.<name>_change_per_window            diagnostic
sensor.<name>_learned_<object>_weight      diagnostic
sensor.<name>_counts_per_gram              diagnostic
sensor.<name>_load_cell_raw                diagnostic
button.<name>_calibrate

Example automation

Glow orange if the object is still sitting there five minutes later, and stop when it is picked up.

automation:
  - alias: "Plate: nag if left behind"
    triggers:
      - trigger: state
        entity_id: binary_sensor.presence_plate_object_on_plate
        to: "on"
        for: "00:05:00"
    actions:
      - action: light.turn_on
        target:
          entity_id: light.presence_plate_object_ring
        data:
          brightness_pct: 60
          rgb_color: [255, 90, 0]
          effect: "Orbit"

  - alias: "Plate: stop nagging once taken"
    triggers:
      - trigger: state
        entity_id: binary_sensor.presence_plate_object_on_plate
        to: "off"
    actions:
      - action: light.turn_off
        target:
          entity_id: light.presence_plate_object_ring

Tuning

SymptomSettingChange toNote
Placements are missedstep_window_ms800–1000Watch 'Change per window' while placing. The spike must exceed learned weight × 0.75.
False eventsstep_fraction0.9
Double triggers on one placementevent_lockout_msraise
Calibration reads lowcal_settleraiseMinimum 800 ms
Tap rhythm never matchescell_polltap slower, or 15 ms with the 80 SPS mod80 SPS mod: cut the trace tying HX711 pin 15 to GND, tie pin 15 to DVDD, halve the four tap windows
Weight reads wildly wrongcell_rating_gmatch your cellCheck 'Counts per gram' against the table in the config

Calibration

The light-colour language

The device learns what your object weighs by watching you put it on three times. Fifteen seconds. The ring tells you what to do at every point, so there is nothing to memorise except two colours.

Blue means put it on. Amber means take it off.

Starting it

Tap deliberately slowly. The sensor is read ten times a second, and a quick tap can land entirely between two readings.

What the ring is telling you

Ring stateMeaningDuration
Amber, fillingClear the plate4 seconds
Blue, breathingPut the object onWaits up to 45 s
Green pixels appearSample captured5 pixels per sample, 3 samples
Amber, breathingTake the object offWaits up to 45 s
Green flash then orbitDone~15 s total
Red flashTimed out, nothing changedStart again
Red slow orbit at bootNever calibratedRuns until calibrated
Blue slow orbit at bootConnecting to WiFiUp to 120 s

If it fails

A red flash means it timed out and nothing changed. Run it again. The most common cause is leaving the object on the plate during the first amber countdown, which is the "clear the plate" phase.

You should never need the Tare button. Calibration tares itself against an empty plate every time.

Design notes

Dimensions

All measured from the CAD file. Datum is the underside of the case.

FeatureValue
OverallØ90.0 × 24.0 mm
Case outer diameter90.0 mm
Case bore86.9 mm
Case wall thickness1.55 mm
Case floor thickness3.0 mm
Top plateØ85.0 × 3.0 mm
Top plate boss+2.0 mm
Plate to bore radial gap1.0 mm
Plate top below the rim0.88 mm
Plate insert holes2 × Ø3.5 mm, 3.0 mm deep
Plate insert hole positions20 mm and 35 mm from centre
Base screw holes2 × Ø4.3 mm through
Base screw counterboresØ7.0 mm × 3.0 mm deep
Hole spacing, both ends15.0 mm
Load cell pad, proud of floor2.5 mm
ESP pocket17.8 × 24.0 mm
ESP header holes2 × 8 at 2.54 mm pitch
Load cell envelope80.1 × 12.7 × 14.3 mm

Choosing a load cell

Drift is about 0.05% of a cell's full scale regardless of what is on the plate, so a 5 kg cell drifts around 2.5 g whether it is weighing 2 g or 2 kg. The step detection makes that survivable, but for objects under about 20 g a 100 g or 200 g cell is worth more than any amount of firmware. The bar in this enclosure is an 80 mm, 15 mm-hole-spacing form factor; 100 g cells exist in the same footprint.

Known issues — Rev A

  1. Heat-set insert length. The STEP file has a part named Insert M3 D5 L5mm sitting in the Ø3.5 mm holes — a CAD library placeholder that cannot physically fit. Use 3 mm long inserts. Next revision will raise the plate boss from 2 mm to 4 mm so the hole can be 5 mm deep.
  2. CAD placeholders. The load cell in the STEP file is embossed 20 kg and the base fasteners are modelled as 8-32 UNC tapping screws. Neither is what gets built — build with M4, and whatever cell rating you bought. Do not take dimensions off those two parts.
  3. Ghost setting in the firmware. baseline_rate_g appears twice in the tuning comments but isn't read by any code, and the "creep the baseline" behaviour it implies isn't implemented. Ignore both — re-baselining on every event is what actually handles drift.

Fixed since the first draft: the step detector's third if block, which tested an undeclared variable thr, has been removed; the device name and the entity names in the comments now match what the file actually ships. The firmware compiles.

Downloads

Everything, to build your own

Full build guide, printable Every step, every table, every callout — 11 pages, ready to print or read offline.
↓ Download PDF
3D models 4 files
Firmware 1 file
Build instructions 3 files

Designed by Pawin Jirasuntornsiri. Firmware MIT licence, hardware CC BY-SA 4.0. Attribution appreciated, not required.