Camera analytics API¶
The Analytics tab works through the central request POST /central/api-v4/streams/{stream}/onvif-call/analytics/{method}. An external system uses the same request. Central forwards the call to the streamer that ingests the camera video, and the streamer forwards it to the camera.
Before you start¶
The request is available to the superadmin and admin roles. The stream must be running, and the camera must be prepared as described in Stream preparation. The examples require Bash, curl, and jq. The responses in the examples come from an Optimus camera; another camera has its own set of types and settings.
export CENTRAL_URL='https://central.example.com'
export API_LOGIN='admin'
export API_PASSWORD='your-password'
export STREAM='entrance'
analytics() {
curl --fail-with-body --silent --show-error \
--user "$API_LOGIN:$API_PASSWORD" \
--header 'Content-Type: application/json' \
--data "$2" \
"$CENTRAL_URL/central/api-v4/streams/$STREAM/onvif-call/analytics/$1"
}
A stream name with a slash is passed encoded: cams%2Fentrance.
Methods¶
Method names and bodies follow the ONVIF Analytics service written as JSON. XML attributes carry the $ prefix; Optic adds the analytics configuration token itself.
GetSupportedRules— the camera's rule types, their count, and incompatible scenarios.GetRuleOptions— limits of rule settings.GetRules— the camera's rules.CreateRules,ModifyRules,DeleteRules— create, change, and delete rules.GetServiceCapabilities— what the camera's analytics service supports.GetSupportedAnalyticsModules,GetAnalyticsModuleOptions,GetAnalyticsModules,ModifyAnalyticsModules— analytics modules, for example the motion detector grid and sensitivity.
Other write methods are rejected with the unsupported_method code. Every attempt to write rules is recorded in the central audit log with the stream_onvif.rules_write action.
Camera capabilities¶
Rule types, their maximum count, and incompatible scenarios:
analytics GetSupportedRules '{}' | jq '.SupportedRules.RuleDescription[]
| {type: ."$Name", max: ."$maxInstances", excludes: .Extension.Excludes}'
{"type":"tt:LineDetector","max":"4","excludes":["tt:LineCounting","tt:ObjectAbandoned","tt:ObjectRemoved","cus:CrowdDetector","cus:QueueDetector","tt:ObjectDetection","tt:LicensePlateRecognition"]}
Setting limits of one type:
analytics GetRuleOptions '{"RuleType":"tt:LineDetector"}' | jq '.RuleOptions'
[{"$Name":"Direction","$RuleType":"tt:LineDetector","StringList":"Right Left Any"},
{"$MaxItems":"3","$Name":"ClassFilter","$RuleType":"tt:LineDetector","StringList":"Human Vehicle NonMotorVehicle"},
{"$Name":"Sensitivity","$RuleType":"tt:LineDetector","IntRange":{"Max":"100","Min":"1"}}]
The streamer keeps the camera capabilities in memory. To read them from the camera again, for example after a firmware update, add the x-onvif-fresh: true header.
Reading rules¶
analytics GetRules '{}' | jq
In the response, Extension.Executor names the executor: optimus for the Optimus adapter, camera for standard ONVIF. Extension.Clock.UtcOffsetMinutes is the camera clock offset from UTC; the schedule is set in these hours.
Creating, changing, and deleting¶
Create a line. The rule name <type>_<number> is assigned on creation and returned in RuleName:
analytics CreateRules '{"Rule":[{"$Type":"tt:LineDetector","Parameters":{
"SimpleItem":[{"$Name":"Armed","$Value":"false"},{"$Name":"Direction","$Value":"Any"}],
"ElementItem":[{"$Name":"Segments","Polyline":{"Point":[{"$x":-0.5,"$y":0},{"$x":0.5,"$y":0}]}}]}}]}'
{"Extension":{"Executor":"optimus","NotApplied":[]},"RuleName":["LineDetector_2"]}
Arm the line with a weekday schedule from 9 to 14:
analytics ModifyRules '{"Rule":[{"$Name":"LineDetector_2","$Type":"tt:LineDetector","Parameters":{
"SimpleItem":[{"$Name":"Armed","$Value":"true"}],
"ElementItem":[{"$Name":"Schedule","HourSchedule":{
"Mon":[[9,14]],"Tue":[[9,14]],"Wed":[[9,14]],"Thu":[[9,14]],"Fri":[[9,14]],"Sat":[],"Sun":[]}}]}}]}'
If the camera declared incompatible scenarios (Extension.Excludes) and the window overlaps one of them, the write is rejected: the response is 422 schedule_overlap, see Errors.
Delete the line:
analytics DeleteRules '{"RuleName":["LineDetector_2"]}'
The motion detector sensitivity is set in the analytics module:
analytics ModifyAnalyticsModules '{"AnalyticsModule":[{"$Name":"CellMotionEngine_1","$Type":"tt:CellMotionEngine",
"Parameters":{"SimpleItem":[{"$Name":"Sensitivity","$Value":"6"}]}}]}'
Write rules:
- Coordinates — from −1 to 1 on both axes, origin at the frame center, the y axis points up.
- Schedule — an
ElementItemnamedSchedule: all seven days (Mon…Sun), intervals[start, end)in whole hours of the camera clock. An empty day disables the scenario on that day. On Optimus cameras, the schedule is shared by all rules of the type. - Permanent rules (type with
$fixed="true") —CreateRulesis rejected;DeleteRulesdisarms the rule. ModifyRules— changes only the settings passed.Extension.NotApplied— settings the camera accepted but did not save, see Response extensions.
Response extensions¶
What the ONVIF standard has no place for, Optic passes as extensions:
Extension.Executor— in every response:optimusfor the Optimus adapter,camerafor standard ONVIF.Extension.Excludes— in a type description inGetSupportedRules: types the camera does not keep enabled in the same hours as this one. Cameras on standard ONVIF do not declare them.Extension.Clock.UtcOffsetMinutes— inGetRules: the camera clock offset from UTC; the schedule is set in these hours.Extension.NotApplied— in write responses: one item per setting the camera accepted but did not save.$Ruleis the rule,$Parameterthe setting,$Sentthe sent value,$Readthe value read after writing. An empty list means the camera saved everything.$Sourceof a setting in a type description:extension— the setting is not in the standard, for exampleSensitivity,MinSize, orSchedule;by-analogy— the setting name is taken by analogy with a rule of a similar type.-
$Unitof a setting:pixel— frame pixels;second— seconds;frame-width— a fraction of the frame width.$fixed="true"of a type — the rule is permanent: it cannot be created or deleted, only armed or disarmed.- The
cus:prefix — a type or setting outside the ONVIF standard, for examplecus:RegionEntranceDetectororcus:HourSchedule.
A type description with extensions:
{"$Name":"cus:RegionEntranceDetector","$maxInstances":"4","Extension":{"Excludes":["tt:LineCounting","tt:ObjectAbandoned","tt:ObjectRemoved","cus:CrowdDetector","cus:QueueDetector","tt:ObjectDetection","tt:LicensePlateRecognition"]},
"Parameters":{"SimpleItemDescription":[{"$Name":"MinSize","$Type":"xs:int","$Source":"extension","$Unit":"pixel"}],
"ElementItemDescription":[{"$Name":"Schedule","$Type":"cus:HourSchedule","$Source":"extension"}]}}
A write response in which the camera kept the rule's switch its own way:
{"Extension":{"Executor":"optimus","NotApplied":[{"$Parameter":"Armed","$Read":true,"$Rule":"LineDetector_1","$Sent":false}]}}
Triggers¶
Rule triggers are stored as episodes with episode_type=trigger. The rule code goes in episode_subtype; separate several codes with commas.
curl --fail-with-body --silent --show-error \
--user "$API_LOGIN:$API_PASSWORD" \
"$CENTRAL_URL/central/api-v4/episodes?media=$STREAM&episode_type=trigger&episode_subtype=lcd,enter" | jq
{"episode_id":"7701038866144167249","media":"entrance","opened_at":1793037836008,"closed_at":1793037836008,
"frame_preview":{"sha256":"5c43636115c17e9cdd6810added59741109f637ce529f4d38e06fd2f341e954a","mime_type":"image/jpeg"},
"detections":[],"details":{"type":"trigger","rule":"lcd"}}
Rule codes:
lcd— line crossing;cc— crossing count;enter— region entrance;exit— region exiting;pid— intrusion;sod— abandoned or removed object;cd— crowd;qd— queue;tamper— tampering.
The trigger frame is served by GET /central/api-v4/episodes/{episode_id}/images/{sha256}, with sha256 taken from frame_preview.
Request headers¶
x-onvif-fresh: true— read the camera capabilities again, bypassing the streamer's memory.x-onvif-auth: user:password— camera credentials instead of the ones from the RTSP URL.x-onvif-port— the camera's HTTP port if it differs from the default.
Errors¶
- 400
unsupported_method— a write method outside the analytics list; 400auth_error— thex-onvif-authheader is not in theuser:passwordform. - 401 — invalid Optic credentials.
- 404
not_found— the stream is not running on any streamer. - 409
no_camera_target— the stream has no RTSP source with the camera address. - 422
param_error— a value outside the camera limits; 422schedule_overlap— incompatible scenarios in the same hours.errors[].pathpoints to the field. - 502
auth_error— the camera rejected the login and password; 502onvif_error— the camera returned an error or is unreachable. - 503 — the camera is busy or asks to wait; repeat the request later.
- 504 — the camera did not answer in time. The write may have been applied: read the rules with
GetRulesbefore retrying, otherwise a repeatedCreateRulescreates a second rule.
Example 422 responses:
{"error":{"code":"param_error","message":"tt:LineDetector: MinSize is 8 px, the camera takes 64..1080 px",
"errors":[{"path":["Rule",0,"Parameters","SimpleItem",4,"$Value"],"message":"tt:LineDetector: MinSize is 8 px, the camera takes 64..1080 px"}]}}
{"error":{"code":"schedule_overlap","message":"tt:LineDetector and tt:LicensePlateRecognition are both active on day 0 from 14:00 to 18:00 by the camera clock; the camera keeps only one of them at a time",
"errors":[{"path":["Rule",0,"Parameters","ElementItem",0,"HourSchedule"],"message":"…"}]}}
Central does not retry a call on another streamer after a connection loss: the write to the camera may already be done, and a retry would change it twice.