ONVIF Camera Deployment Guide
ONVIF Camera Deployment Guide
This guide registers an ONVIF camera with Azure IoT Operations. It checks the camera, stores its credentials, creates a device with an ONVIF inbound endpoint, and adds an asset with pan-tilt-zoom (PTZ) actions. For the design behind this integration, see the ONVIF connector ADR.
Prerequisites
- Azure IoT Operations deployed with the ONVIF connector template enabled:
should_enable_akri_onvif_connector = truein Terraform orshouldEnableAkriOnvifConnector: truein Bicep for the 110-iot-ops component - Secret sync enabled for the instance, with access to its Azure Key Vault
- A camera that the cluster can reach, with ONVIF enabled and digest authentication configured
- Azure CLI with the
azure-iot-opsextension, andkubectlaccess to the cluster
Step 1: Check the Camera
Many cameras ship with ONVIF turned off. Turn it on in the camera's web interface, set authentication to digest, and reboot if required.
Use onvif-ptz-check.sh to confirm that the camera responds and to collect the values later steps need:
cd src/100-edge/111-assets/scripts
export CAMERA_HOST=<camera-ip>
export CAMERA_USERNAME=<camera-username>
./onvif-ptz-check.sh services
The script prompts for the password. Note the device service address for the inbound endpoint and the PTZ service address. Then list the media profiles and test movement with a profile that has a PTZ configuration:
export ONVIF_MEDIA_URL=<media-service-address>
export ONVIF_PTZ_URL=<ptz-service-address>
./onvif-ptz-check.sh profiles
PROFILE_TOKEN=<profile-token> ./onvif-ptz-check.sh move
| Result | Meaning | Action |
|---|---|---|
Connection refused | Wrong port or ONVIF service stopped | Check the ONVIF port; common values are 80, 8000, and 8080 |
| Timeout | Firewall or wrong address | Check network connectivity from the cluster |
HTTP 401 | Credentials rejected | Check the username, password, and ONVIF user permissions. If the credentials are correct, the camera might accept only WS-UsernameToken; set digest authentication on the camera or use the connector's username token fallback |
HTTP 400 or 500 with Data required for operation | ONVIF turned off on the camera | Turn on ONVIF in the camera settings |
Step 2: Store the Camera Credentials
The connector reads credentials from a synced secret on the cluster.
-
Add the camera username and password as secrets in the Key Vault that secret sync uses. See Add secrets to Azure Key Vault.
-
Create one synced secret that maps both Key Vault secrets to the
usernameandpasswordkeys:az iot ops secretsync secret set \--instance <instance-name> \--resource-group <resource-group> \--name <camera-name>-credentials \--secret target=username source=<key-vault-username-secret> \--secret target=password source=<key-vault-password-secret>
The device endpoint references these values as <camera-name>-credentials/username and <camera-name>-credentials/password.
Step 3: Register the Camera Device
Use the option that matches how you manage the deployment.
Option A: Azure CLI
az iot ops ns device create \
--name <camera-name> \
--instance <instance-name> \
--resource-group <resource-group>
az iot ops ns device endpoint inbound add onvif \
--device <camera-name> \
--name <camera-name>-endpoint \
--instance <instance-name> \
--resource-group <resource-group> \
--endpoint-address http://<camera-ip>/onvif/device_service \
--user-ref <camera-name>-credentials/username \
--pass-ref <camera-name>-credentials/password
Option B: Bicep With the 111-assets Component
Create camera.bicepparam in the repository root:
using 'src/100-edge/111-assets/bicep/main.bicep'
param common = {
resourcePrefix: '<resource-prefix>'
location: '<location>'
environment: 'dev'
instance: '001'
}
param customLocationId = '<custom-location-resource-id>'
param adrNamespaceName = '<adr-namespace-name>'
param namespacedDevices = [
{
name: '<camera-name>'
isEnabled: true
endpoints: {
outbound: {
assigned: {}
}
inbound: {
'<camera-name>-endpoint': {
endpointType: 'Microsoft.Onvif'
address: 'http://<camera-ip>/onvif/device_service'
authentication: {
method: 'UsernamePassword'
usernamePasswordCredentials: {
usernameSecretName: '<camera-name>-credentials/username'
passwordSecretName: '<camera-name>-credentials/password'
}
}
}
}
}
}
]
Deploy it to the resource group that contains the Azure IoT Operations instance:
az deployment group create \
--resource-group <resource-group> \
--parameters camera.bicepparam
Look up the resource values with:
az customlocation show --name <custom-location> --resource-group <resource-group> --query id --output tsv
az resource list --resource-group <resource-group> --resource-type Microsoft.DeviceRegistry/namespaces --query "[].name" --output tsv
Option C: Terraform With the full-multi-node-cluster Blueprint
When you deploy with the full-multi-node-cluster blueprint, add the camera to the same variables file and apply the blueprint again. The blueprint passes namespaced_devices and namespaced_assets to the 111-assets component. The onvif-connector-assets.tfvars.example file shows a complete configuration.
should_enable_akri_onvif_connector = true
namespaced_devices = [
{
name = "<camera-name>"
enabled = true
endpoints = {
outbound = { assigned = {} }
inbound = {
"<camera-name>-endpoint" = {
endpoint_type = "Microsoft.Onvif"
address = "http://<camera-ip>/onvif/device_service"
authentication = {
method = "UsernamePassword"
usernamePasswordCredentials = {
usernameSecretName = "<camera-name>-credentials/username"
passwordSecretName = "<camera-name>-credentials/password"
}
}
}
}
}
}
]
Step 4: Add the PTZ Control Asset
PTZ control uses an asset with a management group of Call actions. The asset references the device endpoint from Step 3. In the operations experience, you can also create it with Import and create asset on the discovered ONVIF asset.
In Bicep, add this parameter to camera.bicepparam and deploy again:
param namespacedAssets = [
{
name: '<camera-name>-ptz'
isEnabled: true
deviceRef: {
deviceName: '<camera-name>'
endpointName: '<camera-name>-endpoint'
}
attributes: {}
datasets: []
managementGroups: [
{
name: 'ptz'
actions: [
{ name: 'RelativeMove', actionType: 'Call', targetUri: 'dtmi:onvif:ptz:RelativeMove;1' }
{ name: 'ContinuousMove', actionType: 'Call', targetUri: 'dtmi:onvif:ptz:ContinuousMove;1' }
{ name: 'Stop', actionType: 'Call', targetUri: 'dtmi:onvif:ptz:Stop;1' }
{ name: 'GotoHomePosition', actionType: 'Call', targetUri: 'dtmi:onvif:ptz:GotoHomePosition;1' }
{ name: 'GotoPreset', actionType: 'Call', targetUri: 'dtmi:onvif:ptz:GotoPreset;1' }
]
}
]
}
]
In Terraform, add this variable to the blueprint variables file and apply again:
namespaced_assets = [
{
name = "<camera-name>-ptz"
enabled = true
device_ref = {
device_name = "<camera-name>"
endpoint_name = "<camera-name>-endpoint"
}
management_groups = [
{
name = "ptz"
actions = [
{ name = "RelativeMove", action_type = "Call", target_uri = "dtmi:onvif:ptz:RelativeMove;1" },
{ name = "ContinuousMove", action_type = "Call", target_uri = "dtmi:onvif:ptz:ContinuousMove;1" },
{ name = "Stop", action_type = "Call", target_uri = "dtmi:onvif:ptz:Stop;1" },
{ name = "GotoHomePosition", action_type = "Call", target_uri = "dtmi:onvif:ptz:GotoHomePosition;1" },
{ name = "GotoPreset", action_type = "Call", target_uri = "dtmi:onvif:ptz:GotoPreset;1" },
]
}
]
}
]
The connector logs Asset Endpoint is not being observed for an asset that has only management groups. This message is expected.
Step 5: Verify the Deployment
az resource list --resource-group <resource-group> \
--resource-type Microsoft.DeviceRegistry/namespaces/devices \
--query "[].{name:name, provisioning:properties.provisioningState}" --output table
kubectl get devices.namespaces.deviceregistry.microsoft.com --namespace azure-iot-operations
kubectl get assets.namespaces.deviceregistry.microsoft.com --namespace azure-iot-operations
kubectl logs --namespace azure-iot-operations $(kubectl get pods --namespace azure-iot-operations --output name | grep onvif) --tail=100
Step 6: Control the Camera
The connector exposes PTZ operations as MQTT RPC commands on the topic {namespace}/mrpc/{asset}/{commandName}, for example azure-iot-operations/mrpc/<camera-name>-ptz/RelativeMove. A RelativeMove request carries a profile token from Step 1:
{
"RelativeMove": {
"ProfileToken": "<profile-token>",
"Translation": { "PanTilt": { "x": 0.1, "y": 0.0 } }
}
}
MQTT RPC requests need a correlation ID and a response topic, so a plain mosquitto_pub message doesn't get a response. Use a client built on the Azure IoT Operations SDKs, such as the ONVIF PTZ demo. To call actions from the cloud, see Enable and run management actions.
Troubleshooting
Device Missing in Kubernetes
Query the namespaced Device Registry resource types:
kubectl get devices.namespaces.deviceregistry.microsoft.com --namespace azure-iot-operations
kubectl get assets.namespaces.deviceregistry.microsoft.com --namespace azure-iot-operations
Authentication Errors in Connector Logs
- Confirm that the synced secret exists in the
azure-iot-operationsnamespace and hasusernameandpasswordkeys - Confirm that the endpoint references use the
<secret-name>/<key>form - Run
onvif-ptz-check.sh serviceswithK8S_SECRET_NAME=<camera-name>-credentialsto test the synced credentials against the camera
PTZ Commands Have No Effect
- Run
onvif-ptz-check.sh moveto confirm that the camera moves without Azure IoT Operations - Use a profile token whose
PTZcolumn showsyes - Some cameras support only
ContinuousMove; sendContinuousMovefollowed byStop