JSI · Physical Security Engineering · Project EGA

Event Gateway

EGA takes the detection events a camera already produces and turns them into something the CenterV2 operator can see — a box drawn on the live video itself. This is the working logbook: what we learned, what we measured, what we changed, and what is still open.

Project
EGA
Owner
Luis Huerta
Opened
2026-08-16
Updated
2026-08-19
Phase
Pilot running
Pilot camera
172.16.0.36

1Objective

Between 18:00 and 06:00, when a Dahua camera detects a person or a vehicle, the CenterV2 monitoring agent should see a bounding box drawn on the live video.

The constraint that shapes every decision below: CenterV2 will not render third-party metadata. Any design that ships box coordinates alongside the video and hopes the client draws them is dead on arrival.

The box has to be in the pixels. Once it is in the pixels it survives GeoVision, the forward to CenterV2, and the recording — nothing downstream has to understand it.

2Where the project stands

No Can the camera do it alone

Settled with a full config dump. The overlay node simply does not exist on this hardware.

5.3% CPU per camera, hardware encode

One core's worth covers roughly 18 cameras. The video path is affordable.

−0.8s Event-to-frame lag, measured

Correct for it and the box sits on the target. Proven on video.

200× RAM saved by buffering packets

18 MB instead of 4 GB across twelve cameras. This decides the trailer hardware.

ItemStateNote
Camera can burn boxes itselfRuled outEvidence in §4.1
Box coordinate formatConfirmedVerified arithmetically against a live event
Pilot recorderProvenRan live 2026-08-19; alignment confirmed on video
Live RTSP proxyRunningBuilt and publishing 2026-08-19
GeoVision integrationConnectedReading the proxy stream over TCP on port 554

Open questions

QuestionWhy it mattersBlocking
GeoVision channel licensing The recommended design needs two channels per camera. At 12 cameras that is 24. Needs answer
Substream keyframe interval Currently one every 4 seconds. GeoVision would show black on connect, which reads as a dead camera. Needs approval
MediaMTX download Required to serve the RTSP stream the proxy produces. Needs approval
Email alerting Deferred by the operator. No credentials stored anywhere. Parked

3Architecture decisions

3.1  A re-encoding proxy is required

Two approaches were rejected. Having the camera burn the boxes is not supported on this hardware (§4.1). Shipping metadata to CenterV2 fails because it will not render it.

Accepted: decode the camera substream, draw, re-encode, republish as RTSP. GeoVision accepts a generic RTSP source, which makes this clean to wire up.

Dahua substream (RTSP) ↓ decode Draw the boxesraw frames ffmpegh264_qsvpublish MediaMTX  →  GeoVision pulls rtsp://proxy:8554/cam36
Dahua camera 172.16.0.36 mainstream 1080p GeoVision ch. A native driver Recording clean evidence substream D1 events EGA proxy decode · draw · encode MediaMTX port 554 GeoVision ch. B generic RTSP CenterV2 live, boxed evidence path — untouched annotated path — everything below is new
Two channels per camera. Everything above the line already exists and is not modified; everything below it is what this project adds. If the proxy dies, the recording is untouched — only the operator's annotated view goes dark.

3.2  Two GeoVision channels per camera

Channel A connects straight to the camera mainstream and is what records. Evidence stays clean, with no synthetic annotation burned into it, and if the proxy dies the recording is untouched. Channel B carries the proxy output and is the only channel mapped to CenterV2 for live view.

The cost is double the channel count. The alternative — a single channel through the proxy — puts synthetic marks into evidence video and makes the proxy a single point of failure for evidence. That trade is a management call, not an engineering default.

3.3  The live proxy has to encode continuously

Corrects an earlier assumption

The clip recorder can sleep when the scene is empty. The live proxy cannot: a VMS treats a stream that stops as a failed camera and raises an alarm. Decode plus encode is a permanent floor whenever the proxy is up, not an event-triggered cost.

The obvious optimization — pass the stream through untouched while idle and only re-encode during detections — does not work. Switching codecs mid-session changes the stream headers and breaks timestamp continuity, so the client disconnects. That is exactly the failure we were trying to avoid.

3.4  The box the operator sees comes from our detector, not the camera

The camera's metadata is event-shaped, not track-shaped (§4.3). It cannot produce a box that follows a person across frames. It is valuable as a trigger — "look here now" — but the box drawn on screen should come from the verified Hailo detector.

Drawing unverified camera boxes would give false positives visual legitimacy on the operator's screen. That could make alert quality worse, not better, which is the opposite of the point.

3.5  Trailer hardware: video plumber, not brain

The Intel N150 has four efficiency cores and Quick Sync. It handles the video path if we use hardware encoding rather than software (§5.2). It cannot run continuous neural detection for several cameras — no usable NPU, weak integrated graphics. Boxes have to arrive from the camera or from the Hailo box over the network.

4Research findings

Everything below came from device 172.16.0.36 — an IPC-HFW5442E-Z4E on firmware 2.840.0000000.13.R, build 2022-01-10.

4.1  The camera cannot burn boxes into the encoded stream Confirmed

A full read of the camera's overlay configuration returns only channel title, timestamp, custom titles, privacy masks, the PTZ on-screen displays, temperature and voltage. There is no rule-overlay or target-overlay node, so there is no flag to switch on for detection boxes.

This explains something worth internalizing: the green boxes visible in the Dahua web interface are not in the video. The browser draws them from a parallel metadata feed. The pixels never carried them, which is why GeoVision has never shown them and why no camera setting will produce them.

Not yet verified

Whether a newer WizSense S3/S5 model behaves differently. Worth one check before treating this as a fleet-wide rule.

4.2  Box coordinate format Confirmed

Boxes arrive as corners — not position-plus-size — in Dahua's normalized 0–8192 space rather than pixels. This was verified, not inferred: the payload's own center field is exactly the midpoint of the box it ships with.

SourceFieldFormatSpace
Camera tripwireObject.BoundingBox[x1, y1, x2, y2]0–8192
Camera smart motionobject[].Rect[x1, y1, x2, y2]0–8192
Frigate HTTP APIdata.box[x, y, w, h]0–1
Frigate MQTTafter.box[x1, y1, x2, y2]pixels

The two camera sources disagree with each other. For the same tracked object they reported different rectangles — different detectors, different instants. They must never be averaged or mixed.

These are the third and fourth box formats in the wider Jatagan work. Confusing formats has already produced one false conclusion on the AI Box project, so the rule now is to name the source before reasoning about any box.

4.3  What the metadata does not contain Confirmed

Across four real tripwire events:

  • No confidence score. The field is present but always zero. There is nothing to threshold on.
  • No trajectory. The track array is empty every time.
  • No tracking. The start and stop events carry the identical frozen rectangle. It is a snapshot at the moment of crossing, not a box that follows anyone.

This is what forces decision 3.4.

4.4  Timestamps align to the millisecond Confirmed

Each event carries a presentation timestamp that correlates with the payload clock to within a few milliseconds across sampled pairs. This is the mechanism for placing a box on its exact frame without guessing at network latency.

4.5  Traps worth writing down

TrapConsequenceStatus
The smart-motion object ID field is misspelled in the firmware The parser has to misspell it too, deliberately Confirmed
The event clock field appears to carry local time dressed as UTC Reading it as true UTC would shift alignment by whole hours Inferred — calibrate
Command-line tooling treats array indices in a URL as wildcards A configuration write silently failed and returned an empty body Confirmed & solved

4.6  Side findings on the tripwire rules

Outside this project's scope, but observed in the same captures and directly affecting alert quality, so recorded here rather than lost.

  • The active rule carries eight vertices for what should be a two-point tripwire, with the last two jumping back across the scene. The camera evaluates all eight.
  • Observed consequence: two different people each triggered two crossings in opposite directions about two seconds apart. Two out of two. Every person crossing that rule may be generating two alerts. The fix is redrawing the tripwire in the camera, not in code.
  • Both active rules have no size filtering at all, so a two-pixel object at the back of the scene qualifies the same as a person at ten meters.

5Measurements

All taken on the engineering workstation (28 cores) against the pilot camera, D1 substream. The trailer hardware is weaker, so these are ratios to scale from, not absolute budgets.

5.1  Idle path — listener plus rolling frame buffer

Substream rateOne cameraTwelve cameras
30 fps2.3% of a core0.27 cores
15 fps1.4% of a core0.16 cores

The drop is not exactly half because the event connection costs the same either way.

5.2  Encoder benchmark — one camera, D1 at 15 fps

PathCPUTwelve cameras, always on
Pass through, no decode0.26% of a core0.03 cores
Software encode7.6%0.91 cores
Hardware encode (Quick Sync)5.3%0.64 cores

NVIDIA encoding is compiled into our ffmpeg but unusable on this workstation — there is no NVIDIA GPU in it. On the N150 expect software encoding to scale far worse and Quick Sync to hold roughly steady, since it is a dedicated block rather than general-purpose cores.

5.3  Memory — the twelve-second pre-roll buffer

A clip has to start before the trigger, otherwise the video begins after the subject has already walked into frame. That means keeping recent video in memory at all times. How we keep it is the single most consequential decision for the trailer hardware.

Buffer contentsOne cameraTwelve cameras
Decoded frames at 30 fps~350 MB4.08 GB
Decoded frames at 15 fps~174 MB2.04 GB
Compressed packets~1.5 MB18 MB

A 200× difference. An N150 machine typically ships with 8–16 GB, so four gigabytes of buffers alone is not viable and two is uncomfortable. Eighteen megabytes is free.

6Pilot results

The pilot ran live against the parking-lot camera on the morning of 2026-08-19. It was never meant to prove we can draw a rectangle — that was never in doubt. It was built to answer one question: does the box land on the right person at the right frame?

Clip 010 · 2026-08-19 08:25:57 · person crossing the tripwire, three detection events. The box is the camera's own estimate, burned into the pixels by the pilot. Recorded with our first offset guess, which is why the box appears slightly early — this footage is what the corrected figure was measured from.

Reading the alignment off the video

FrameTimestampWhat is on screen
6208:25:55.660Box drawn; the person is still to the right of it
7808:25:56.704The box lands on the person
9408:25:57.788The person has walked past; the box sits behind them

The detection event reached us at 08:25:57.508, but it describes the frame at 08:25:56.704. That is a lag of −0.8 seconds between the event and the video it refers to. Correct for it and the box sits on the target.

Why this number does not ship

The 0.8 seconds includes this workstation's network buffering. On the trailer hardware it will be a different figure. The production answer is the presentation timestamp carried inside each event (§4.4), which points at the exact frame regardless of anyone's latency. This measurement proves the alignment is achievable; it is not a constant to hard-code.

What the window produced

MeasureResult
Detections captured15
Clips recorded16
Event blocks dropped0
Object types seenHuman and Vehicle
Usable window after three defects~65 of 120 min

The concept is proven. A box can be placed on the correct target on the correct frame, burned into video that any downstream system will carry without understanding it. The risk that could have killed the proxy is cleared.

Making the box follow the subject

The first clip exposed a limit: each box is frozen at the instant of its detection, so a subject walking through frame produced a rectangle that blinked in place while they moved past it. That looked like the ceiling of what camera data could do. It was not.

Every event carries an object id, and the camera keeps it stable across event types. One person walking to a tripwire produced three events under a single id, with the box at a different position each time. We were receiving a coarse track and discarding it by drawing each box on an independent timer. Grouping the samples by object and filling the gaps between them turns three blinks into one continuous box that moves with the subject.

Clip 002 · 2026-08-19 09:29:18 · the same object id reported three times over 2.3 seconds; the box glides between those samples instead of jumping. Recorded before the motion pulse was added, so the border here is the plain thin outline.

The camera's own coverage reaches further than expected: the first event fired 1.4 seconds before the subject reached the tripwire, so the box is already on them as they approach, not only at the moment of crossing.

Two honest limits. Interpolation assumes constant velocity between samples, so a subject who reverses direction mid-gap will trace a slightly wrong path. And drawing a frame requires knowing the box that comes after it, so frames are held ~2 seconds before being rendered — on a live stream that is real delay for the operator, and it has to be accepted deliberately rather than discovered later.

A second job for the box: making the VMS see it

Locating the subject for the operator is only half of what the box is for. The other half is mechanical: GeoVision's motion detection has to fire so the event is guaranteed to reach CenterV2. Rather than hope the scene trips it, the box manufactures the motion by pulsing between red and amber — two colours far apart in brightness, which is what a motion detector actually measures.

Contrast was never the problem. Area was. Measured against this scene, where wind and shadows already move 0.60% of the frame between consecutive frames:

Box stylePixels changed% of frame
2 px outline (original)1,0160.30%
6 px outline2,3600.70%
10 px outline3,7001.09%
6 px outline + solid corner patch10,2433.03%

An outline has almost no area by definition. The original 2 px box changed half as many pixels as the wind did — invisible to any detector. Even at 10 px it barely matches the noise. So the solid corner patch became the mechanism and the border its companion: together they clear the scene's own noise by more than six times.

Clip 002 · 2026-08-19 09:46:18 · 44 seconds, 8 detections, three separate objects — one vehicle and two people, each tracked under its own id. The pulsing block in the upper left is the motion signal; the box border pulses with it.

The border and the patch deliberately pulse at different rates. The border throbs at 2.5 Hz — calm enough for someone watching monitors all shift. The patch alternates on every single frame, so the full colour step sits between every consecutive pair. Pulsing both slowly left two of every three frame pairs nearly static, which would fail a detector that needs sustained rather than intermittent change.

Recorded so nobody misreads it later

This injects synthetic motion into video that GeoVision will then report as motion detected. The originating detection is real, so no false alarm is manufactured — but the resulting VMS event looks like independent corroboration and is not. Channel A keeps recording clean from the camera, and the mechanism is written down here so no future reader treats the VMS log as a second opinion.

Two things remain unproven. All of this is measured against our own motion estimator, not GeoVision's — the real test is pointing the VMS at the stream. And the camera's event cadence is irregular: gaps of 1.4 seconds are common but 5-second gaps happen, and a frame can only be interpolated once the box after it has arrived. Where the gap outruns that window the box drops out mid-transit, which is visible in the clip above. Holding the last position longer papers over it; the real fix is a tracker that does not wait on events.

events, as they reach us event event describes a frame 0.8 s earlier video frames box interpolated across this gap frames held 2 s ← nothing can be drawn until the next box arrives
Why the stream is delayed. A frame cannot be drawn the moment it arrives: interpolation needs the box that comes after it. Holding frames two seconds buys smooth motion and costs two seconds of latency — measured at 2.46 s end to end, against 0.47 s with interpolation switched off.
outline on the object 0.30% of the frame below the noise floor — invisible band at the picture edge 2.09% of the frame same signal, covers nothing scene noise 0.87% wind, shadows
Why the motion marker is shaped the way it is. An outline has almost no area by definition, so it cannot out-signal the wind. A thin band around the edge carries the same area as a solid corner block while occluding nothing an operator needs to see.

Where per-frame motion would have to come from

The camera reports roughly one box per object every 1 to 1.5 seconds. That is the hard ceiling of camera-supplied data, so anything smoother has to be produced by us.

One possibility was ruled out by measurement. The event stream contains IntelliFrame markers, which suggested the camera might embed object metadata inside the video itself — which would have given per-frame boxes for free. Raw H.264 was dumped from both streams and every embedded data unit inspected: 7 in the substream, 21 in the mainstream, all of a single vendor type carrying one byte that simply counts up once per keyframe. No object data on either stream. That lead is closed and does not need revisiting.

Smoother motion therefore means either the interpolation above, or a lightweight visual tracker seeded by each camera event — cheap enough for the trailer hardware and self-correcting, since every new event re-anchors it.

7Live integration

On the afternoon of 2026-08-19 the chain ran end to end for the first time: camera to proxy to streaming server to GeoVision to the operator's view. What had been a clip recorder became a live source a VMS can consume.

7.1  What runs now

A continuous proxy decodes the camera substream, draws tracked boxes, and publishes H.264 over RTSP to a local streaming server that GeoVision pulls from. Unlike the clip recorder it never sleeps: a VMS reads a stream that stops as a failed camera, so decode and encode are a permanent floor rather than an event-triggered cost.

Clip 059 · 2026-08-19 14:30:27 · the shipping form, with everything applied at once: one subject tracked across four detections under a single id, the box gliding between samples, and the edge band pulsing red to amber to keep the VMS's motion detector awake.

7.2  Measured latency

ConfigurationMeasuredPredicted beforehand
Smooth motion (frames held 2 s)2.46 s2.4 s
Responsive (no hold, box steps)0.47 s0.4 s

The gap between the two is 1.99 s — exactly the hold, which is what makes the measurement trustworthy. Method: the proxy burns each frame's capture time into the picture, so reading that stamp at the far end measures everything the proxy adds. A first reading of 4.05 s was discarded because it included the cost of opening a cold connection.

The delay is a choice, not a limit. Eighty-three percent of it is the deliberate hold. The camera only reports a box every 1 to 1.5 seconds either way, so interpolation adds no information — only smoothness. For a live operator, half a second is a better trade than two and a half.

7.3  The integration failure worth recording

Connecting GeoVision took about an hour, and almost all of it was spent looking in the wrong place. The stream was published on port 8554 and the URL handed to GeoVision said 8554, but GeoVision ignores the port in the URL and connects on 554, the RTSP default.

The failure mode is what made it slow: a client calling a closed port leaves no trace anywhere. The server log stayed empty, which looked like evidence that nothing was attempting to connect. Hours went into the session description, the encoder profile, the firewall, and the third-party licence — all of them fine. Moving the server to port 554 produced a connection within seconds.

The lesson

An empty log proves that nothing reached that log. It does not prove that nothing was sent. When a component reports it is trying and the receiver sees nothing, the first question is whether both are talking about the same address — not what is wrong with the payload.

Along the way one real defect surfaced and was fixed: the proxy had the port written into its startup check by hand, so moving the server left it refusing to start against a port nobody was listening on. It now derives the port from the address it publishes to.

8Change log

8.1  Changes to live equipment

DateDeviceChangeReversibleVerified
2026-08-19 172.16.0.36 Substream frame rate 30 → 15. Mainstream untouched. Yes Config re-read & stream confirmed

Nothing else on any camera, recorder or remote host has been modified by this project. Every other device interaction was read-only. The frame rate change was explicitly authorized in session; the pending keyframe change is not covered by that authorization and will be raised separately.

8.2  Local artifacts created

PathPurpose
C:\Users\lhuer\jatagan-bbox\Project root
  bbox_clip_pilot.pyThe pilot: listener, buffer, recorder, notifier
  synthetic_test.pyInjects a fake detection so the record path can be validated without waiting for a person
  measure_idle.pyIdle CPU measurement
  measure_ram.pyBuffer memory measurement
  LOGBOOK.mdFull local version of this document
C:\Users\lhuer\.jatagan\Credentials, deliberately outside any project folder so they never reach a repository
E:\jatagan-bbox-pilot\Clip output, sidecar data, run log

8.3  Defect found and fixed during the pilot

Clip frame rate was hardcoded while the live substream ran at a different rate, so clips would have played at roughly half speed. The buffer now measures the real arrival rate and writes the clip at that rate. Caught by test T5, confirmed fixed by T6.

9Corrections to earlier conclusions

Kept deliberately. Each of these was believed and acted on before being disproven, and the record is more useful with them than without.

9.1  The camera does not give us a free trajectory

An earlier note recorded that tripwire events carried the camera's own computed trajectory, described as "a free second opinion." Four real events show it empty every time. Any design leaning on camera-supplied tracking had no foundation.

9.2  Buffering compressed packets — right call, wrong reason

First stated as the top processor optimization. Then measured: continuous decoding costs 1.4% of a core, so as a processor optimization it is nearly pointless, and it was deprioritized. Then measured from the memory side, where it is a 200× difference.

It is back to priority one — as a memory decision, not a processor one. On a 28-core workstation with abundant RAM the problem is invisible. On the trailer hardware it decides whether the system runs at all.

9.3  "An empty scene costs nothing"

True for the clip recorder, false for the live proxy. See 3.3.

9.4  Three defects in the measuring instrument itself

The pilot lost roughly 55 minutes of a 120-minute window to three defects in the listener, all of them ours. They are recorded because the failure mode matters more than the fix.

DefectEffectCost
Header/body split assumed Unix line endings; the camera sends Windows ones Every event silently discarded 45 min
Read buffer sized larger than the traffic could fill A detection could sit unread for minutes on a quiet stream
First attempt at fixing the buffer made it block forever Stream completely dead 10 min
The lesson worth keeping

For 45 minutes the status display read "connected" while the listener understood nothing at all. A green light that only proves a socket is open is not a green light. The status line now counts blocks received and blocks dropped separately, so a connected-but-deaf stream announces itself instead of looking like a quiet night. This project exists to stop physical-security work from failing quietly, and the measuring instrument had exactly that flaw built into it.

The working answer was already in the building: the tripwire notifier in the earlier snapshot project has read this same stream correctly for months. Reading it first would have saved all three.

10Resource inventory

Workstation

ResourceDetail
Cores28
Python3.13.2 — imaging and HTTP libraries present; PyAV not installed (needed for the packet-buffer rewrite)
ffmpeg8.1.1, bundled with Jatagan Footage Editor
Encoders availableSoftware and Intel Quick Sync both working; NVIDIA present but unusable
External diskSamsung T7 Shield, 931 GB, 786 GB free

Devices

DeviceAddressNotes
Pilot camera172.16.0.36IPC-HFW5442E-Z4E, on-screen label AIC-0-36 / D2P1-184
Hailo filter PC172.16.10.61Remote shell is PowerShell 5.1. No email configuration exists there, contrary to an early assumption
GeoVision recorder172.16.11.245Accepts generic RTSP sources

Credentials are never recorded in this document.

Existing code worth reusing

Two files in the earlier snapshot project turned out to cover most of the pilot's ground. tripwire_email_notifier.py already implements the event listener, a video ring buffer, box drawing in the camera's coordinate space, and email with anti-spam throttling. ivs_watch.py contains a tuned algorithm for choosing which frame a detection belongs to — prior art on exactly the alignment problem this project has to solve.

11Test log

#DateTestResult
T108-1615 s event captureHeartbeats only. Endpoint alive, format not confirmed
T208-16100 s event captureHeartbeats only. Reported as a null result rather than rounded up
T308-169 min event captureTwo people captured. Six usable events. Sections 4.2–4.6 all come from this
T408-19Preflight checkCamera, ffmpeg, disk, RTSP all pass. Verdict READY
T508-19Synthetic clip, 30 fpsDefect found — frame rate mismatch, clip would play at half speed
T608-19Synthetic clip after fix363 frames, 11.37 s, rate measured correctly
T708-19Visual check of a drawn frameBox renders correctly; label and timestamp present; camera on-screen display intact
T808-19Idle CPU at 30 fps2.3% of a core
T908-19Camera set to 15 fps, re-measureConfirmed arriving at 15.0 fps; idle CPU 1.4%
T1008-19Synthetic clip on 15 fps stream194 frames, 11.28 s. Correct
T1108-19Encoder benchmarkSee section 5.2
T1208-19Live pilot window, real foot traffic15 detections, 16 clips, 0 dropped. Alignment measured at −0.8 s. See section 6

12Next steps

  1. Review the pilot output. The question it answers is whether the box lands on the right person at the right frame — not whether we can draw one, which is already settled.
  2. If alignment holds: stand up the streaming server, build the live proxy, and prove the chain end to end into GeoVision.
  3. Shorten the substream keyframe interval before the proxy work. A VMS showing four seconds of black on connect reads as a dead camera.
  4. Confirm GeoVision channel headroom, which decides the recording architecture.
  5. Move the pre-roll buffer from decoded frames to compressed packets before anything ships to the trailer. This is the 200× memory decision.