Talking to Cameras
The engine's activity functions say "move this camera" or "get me an image." Behind those
calls is a physical Axis camera on a pole somewhere, reachable only over its own quirky HTTP
API (VAPIX), sitting behind a self-signed certificate, demanding digest authentication, and
occasionally not answering. Alert.CA.VAPIXService is the one place all of that mess is
handled, so the rest of the platform can just call a method.
The one idea to hold on to
Every camera call goes through a single typed HttpClient pipeline, and each stage solves
one of the camera's inconveniences. You inject IVAPIXClient, call
PanTiltZoomAsync/GetImageAsync/ToggleGuardTour, and the pipeline does the rest:
That whole chain is wired once in AddVAPIXHttpClient. Three stages worth understanding:
- The certificate stage accepts anything. Cameras ship self-signed certs, so the primary
handler's
ServerCertificateCustomValidationCallbackreturnstrueunconditionally. It's insecure by design — the alternative is not talking to the cameras at all — and it's isolated to this client so nothing else inherits it. - The auth stage speaks both dialects. For basic auth it sets the header and sends. For
digest it does the real handshake: send unauthenticated, read the
401challenge (realm, nonce, qop), compute the digest header, and resend. The device'sAuthTypepicks the path. - The resilience stage bounds failure. A
vapix-pipelinehandler adds a 30-second timeout and retries twice (exponential backoff, 2s) — but only on transient faults (5xx or network exceptions), never on a401or404where retrying is pointless.
The retry policy's ShouldHandle fires solely on HttpRequestException or a 5xx status. A
misconfigured credential (401) or a wrong path (404) fails immediately with no retry —
correct, because hammering a camera that authoritatively said "no" wastes the 30s budget and
the camera's limited request capacity. If a call isn't retrying, check whether the fault is
actually transient.
Credentials are decrypted at the last moment
A camera call needs a username, password, and address — all stored encrypted on the
AcquisitionDeviceEntity in Cosmos. VapixRequestBuilder.BuildRequestBase is where they're
turned back into usable values, and only if the device is flagged encrypted:
Username = acquisitionDeviceEntity.IsEncrypted
? await encryptionService.DecryptAsync(credential.Username) : credential.Username,
That DecryptAsync is the AES-CBC decrypt from the Infrastructure encryption service — the
same key machinery documented there. Plaintext credentials exist only for the moment a request
is built, never at rest.
Wrapping up
VAPIXService is the single door to physical Axis cameras: one typed HttpClient whose
stages accept the self-signed cert, perform basic or digest auth, and retry transient faults
within a 30-second budget — fed by a request builder that decrypts credentials at the last
moment. Everything else calls IVAPIXClient and stays ignorant of the camera's quirks.
Not every "camera" is real, though. Next: Simulated Cameras.