← Back to the blog
Plugins #safety

Recipe: workplace safety with PPE and presence monitoring

Flag missing helmets and vests in a work zone during work hours, and catch an empty station or after-hours presence. Two safety apps — with an honest note on which is experimental.

3 min read
Leer en español

Why safety monitoring

Two safety questions recur on a site: is everyone wearing their protective gear, and is the right person at their post (and nobody there when they should not be)? StreamHub answers both from cameras you already have — PPE detection for helmets and vests, and presence monitoring for empty stations and after-hours activity. One caveat up front, stated honestly: PPE detection is experimental and advisory, not a certified safety system.

Architecture in words

Both are Studio Apps with Python workers. safety-ppe runs a community YOLOv8 PPE model (via ONNX) over the stream, associates gear with people in zones during configured work hours, and fires ppe.violation with a snapshot when someone is missing a required item. presence runs person detection in rectangular zones and fires presence.absent when a zone stays empty too long, presence.returned when someone comes back, and presence.after_hours when a zone is occupied outside the schedule.

1. PPE detection (experimental)

export BASE="https://your-server.example.com/api/v1"
export TOKEN="sk_...."

curl -X POST $BASE/apps/site/plugins/safety-ppe/install -H "Authorization: Bearer $TOKEN"

curl -X PATCH $BASE/apps/site/plugins/safety-ppe -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
        "enabled": true,
        "config": {
          "room": "dock",
          "requireHelmet": true,
          "requireVest": true,
          "zones": "[{\"name\":\"dock\",\"rect\":[0.1,0.2,0.5,0.7]}]",
          "workHours": "08:00-18:00",
          "workDays": "mon-fri",
          "callbackUrl": "https://safety.example.com/hooks/ppe"
        }
      }'

Key defaults: requireHelmet / requireVest true; zones empty means one full-frame zone; workHours / workDays empty means always on; sustainFrames 3 (fires after more than 3 consecutive detections); cooldownSeconds 120; confidence 0.35; association 0.5; fps 1 (YOLOv8m is ~1s per frame per CPU core — keep it at 1 on CPU). A ppe.violation payload is { event, app, room, ts, zone, missing, confidence, snapshot_path }, where missing is helmet, vest or helmet,vest, signed when callbackSecret is set.

2. Presence monitoring

curl -X POST $BASE/apps/site/plugins/presence/install -H "Authorization: Bearer $TOKEN"

curl -X PATCH $BASE/apps/site/plugins/presence -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
        "enabled": true,
        "config": {
          "room": "front-desk",
          "zones": "[{\"id\":\"desk-1\",\"name\":\"Front desk\",\"x\":0.1,\"y\":0.2,\"w\":0.3,\"h\":0.4}]",
          "schedule": "08:00-18:00",
          "scheduleDays": "mon-fri",
          "thresholdMin": 5,
          "afterHours": true,
          "timezone": "America/Argentina/Buenos_Aires"
        }
      }'

Presence zones are rectangles (x/y/w/h, normalized), not polygons. thresholdMin 5 means a zone empty longer than five minutes fires presence.absent; afterHours true fires presence.after_hours for presence outside the schedule. Events go through the app's signed callbacks (plus an optional raw callbackUrl), and history lands in apps/site/presence/presence.db.

3. Consume the events

Both apps expose their live state in the dashboard app-tab (GET /apps/site/plugins/presence/data), and both POST to your webhook. Route ppe.violation to a supervisor channel, presence.absent to a shift lead, presence.after_hours to security.

What you should see

Someone stepping into the dock without a helmet, during work hours, produces a ppe.violation with a snapshot after a few sustained frames. A front desk left empty past five minutes fires presence.absent; the person returning fires presence.returned; anyone at the desk at 3 a.m. fires presence.after_hours.

Troubleshooting

  • PPE false positives/negatives — expected. The model is community-trained (mAP50 ≈ 0.78), biased toward outdoor construction imagery, and weakest on the "no-gear" classes and small distant hardhats. Treat events as advisory signals; do not build a compliance gate on them alone.
  • PPE CPU spikes — YOLOv8m costs ~1s of a core per frame; keep fps at 1 or below on CPU, or add a GPU.
  • Presence never fires absent — the empty timer anchors at the worker's first sample, so an already-empty zone fires thresholdMin later; confirm the zone rectangle actually covers the seat.

Closing

These run entirely on your hardware, and the PPE app deliberately keeps its evidence local. If you need a hardened safety deployment — validated models, sign-off workflows, integration with a site access system — that is a consulting engagement where we set the right expectations and build to them, rather than shipping an experimental model as if it were certified.