Control4 DriverWorks References / Camera Extras and Dynamic URLs / Markdown

Control4 Subsystem Reference

Camera Extras and Dynamic URLs

Two capabilities a camera driver can opt into from its manifest: building its own control panel at runtime, and handing over stream and snapshot URLs on demand instead of declaring them once. Both are driven entirely with proxy notifications. Extras are not specific to cameras and are worth knowing wherever a driver needs controls its proxy does not define.

Proxy
camera
Binding
5001
Opt in via
<capabilities>
Payloads
XML strings

The model

A camera driver has always been able to declare fixed URLs and a fixed feature set in its manifest. These capabilities invert that. The driver publishes its controls and its URLs at runtime, as XML, over the proxy binding it already owns.

Two independent mechanisms share one opt in style, and a driver may adopt either without the other.

Extras are driver defined controls rendered in the camera interface. The driver sends a document describing sections and objects. Each object names the command the proxy will send back when a viewer manipulates it, so the driver defines both the control and its callback in one place. Nothing about an extra is declared in the manifest, which means the control set can differ per device, per model, or per firmware revision.

Dynamic URLs replace static stream and snapshot addresses. Rather than resolving a URL once at setup, the driver answers with a current list whenever one is needed, and can invalidate the previous answer at any time. This suits devices whose addresses depend on how they are attached, on a credential that rotates, or on a recorder that fronts them.

Both mechanisms are pure notification traffic. There are no new bindings and no new proxies. Everything travels over the existing camera proxy binding, which is conventionally 5001.

Extras are not a camera concept. The same notifications and the same document format are used by drivers across many proxy types, including thermostats, lights, fireplaces and keypads. Only the dynamic URL half is specific to cameras. Read the extras sections below as general proxy technique that happens to be documented here against a camera.

Opting in

Three flags inside the manifest's <capabilities> block. They gate the behavior: without them, the notifications below are ignored.

Capability flags
FlagEffect
has_extras The driver supplies its own control panel. Enables the extras notifications.
requires_dynamic_stream_urls Stream addresses come from the driver at runtime rather than from static configuration.
requires_dynamic_snapshot_urls The same for still images.
<capabilities>
  <modes>H265,H264,SNAPSHOT</modes>

  <has_extras>true</has_extras>
  <requires_dynamic_stream_urls>true</requires_dynamic_stream_urls>
  <requires_dynamic_snapshot_urls>true</requires_dynamic_snapshot_urls>
</capabilities>

The stream and snapshot flags are independent. A device that serves stills from a stable path but streams from a rotating one sets only the stream flag.

Call flow

Camera driver Camera proxy At startup EXTRAS_SETUP_CHANGED { XML } DYNAMIC_CAPABILITIES_CHANGED On viewer input the object's own command named by the command attribute in the setup document EXTRAS_STATE_CHANGED { XML } URL lifecycle STREAM_URLS_READY { KEY, URLS } DYNAMIC_URLS_CHANGED
Almost all traffic runs driver to proxy. The single inbound arrow is the command the driver itself named when it defined the control, which is what makes the extras surface self describing.

Conventions

Everything is a notification

Each message below is an ordinary proxy notification on the camera binding. There is no response and no return value. The driver announces state, and the interface catches up.

C4:SendToProxy(CAMERA_BINDING, "EXTRAS_SETUP_CHANGED", { XML = document }, "NOTIFY")

Payloads are XML strings, not tables

The documents travel as a single string parameter, conventionally XML, built by the driver. This is unlike most proxy traffic, where parameters are a flat table of scalars. Escape any value that could contain a quote or an angle bracket, since device supplied names frequently do.

Identifiers are yours, and they are load bearing

An object's id is chosen by the driver. It is the handle used to update that control's value later, so it must stay stable for as long as the control exists. The command attribute is likewise arbitrary and is simply the command name the proxy will send back. Deriving one from the other keeps the two ends aligned.

Library conventions are not the contract

The examples here dispatch inbound commands through a table looked up by name, which is a common driver idiom rather than a requirement. What is fixed is the notification names, the parameter names (XML, KEY, URLS), the element and attribute names in the documents, and the fact that the inbound command is whatever the driver put in the command attribute. A driver may receive that command in a plain ReceivedFromProxy with a chain of comparisons and behave identically.

Driver notifications

Sent by the driver on the camera proxy binding. All are fire and forget.

EXTRAS_SETUP_CHANGED Extras

Publishes the entire control panel. Send it once the driver knows what the device supports, and again whenever the shape of the panel changes, such as when a capability probe finishes or a viewer changes how the device is attached.

Parameters
NameTypeNotes
XMLstring reqAn extras_setup document. Replaces the previous panel entirely.

This is a replacement, not a merge. Any control absent from the new document is gone.

EXTRAS_STATE_CHANGED Extras

Updates the value of controls already published, without resending the panel. Use it when the device reports a change that did not originate in the interface, and to confirm a change that did.

Parameters
NameTypeNotes
XMLstring reqAn extras_state document carrying one or more id and value pairs.

An id that does not match a published object has no effect. There is no error.

DYNAMIC_CAPABILITIES_CHANGED Capabilities

Announces the movement related capability set, which is carried as an ordinary parameter table rather than a document. Send it alongside the extras panel whenever the driver has finished working out what the device can physically do.

STREAM_URLS_READY URLs

Delivers the current stream list. Required when the driver declares requires_dynamic_stream_urls.

Parameters
NameTypeNotes
KEYstringCorrelates this answer with the request that prompted it. Echoed into the document's own key attribute.
URLSstring reqA streams document.

The key appears in two places, as a parameter and as an attribute on the document root. Set both to the same value.

SNAPSHOT_URLS_READY URLs

The still image counterpart, carrying a snapshots document. Same parameters and same key handling.

DYNAMIC_URLS_CHANGED URLs

Invalidates whatever was published previously. Takes no parameters. Send it when an address, port, credential, or attachment method changes, so that anything holding an old URL discards it.

This is the mechanism that makes the whole dynamic URL arrangement worthwhile. A driver that never invalidates has gained nothing over static configuration.

Proxy commands

Inbound commands arrive on the camera binding and are handled like any other proxy traffic.

the command you named Extras

When a viewer operates a control, the proxy sends the command string from that object's command attribute. The driver chose the name, so there is no fixed vocabulary here, only the promise that what the driver declared is what it will receive.

The new value arrives in the command parameters. Apply it to the device, then confirm with EXTRAS_STATE_CHANGED rather than assuming the interface and the device now agree. A control whose write fails should be pushed back to its old value the same way.

Momentary controls carry no meaningful value. Treat the arrival of the command as the whole event.

Connection and addressing Standard

The usual camera proxy commands still arrive and still matter here, because each one can invalidate a previously published URL. A driver using dynamic URLs should treat every one of these as a trigger to send DYNAMIC_URLS_CHANGED.

Commands that can invalidate a URL
CommandCarries
SET_ADDRESSADDRESS
SET_HTTP_PORTPORT
SET_HTTPS_PORTPORT
SET_RTSP_PORTPORT
SET_USERNAMEcredential
SET_PASSWORDcredential
SET_USE_HTTPSscheme selection
SET_AUTHENTICATION_REQUIREDauth policy
SET_AUTHENTICATION_TYPEauth policy
SET_PUBLICLY_ACCESSIBLEreachability

On an address change the driver also emits ADDRESS_CHANGED back to the proxy carrying the new ADDRESS.

Payload shapes

extras_setup

Sections group controls under a heading. Objects are the controls. Four object types exist: list, slider, switch, and button.

<extras_setup>
  <extra>
    <section label="Illumination">

      <object type="list" id="beam_mode" label="Beam mode"
              command="SET_BEAM_MODE" value="auto">
        <list maxselections="1" minselections="1">
          <item text="Automatic" value="auto"/>
          <item text="Manual"    value="manual"/>
        </list>
      </object>

      <object type="slider" id="beam_level" label="Brightness"
              command="SET_BEAM_LEVEL" value="50" min="1" max="100"/>

      <object type="switch" id="beam_enabled" label="Enabled"
              command="SET_BEAM_ENABLED" value="true"/>

      <object type="button" id="beam_test" label="Test"
              command="SET_BEAM_TEST"/>

    </section>
  </extra>
</extras_setup>
Object attributes
AttributeApplies toMeaning
typealllist, slider, switch, or button.
idallDriver chosen handle. Must stay stable while the control exists.
labelallShown to the viewer.
commandallThe command the proxy sends back on interaction.
valuelist, slider, switchCurrent value. Omit for momentary controls.
min, maxsliderRange bounds.
maxselections, minselectionslistOn the inner list element. Both are 1 for a single choice.

extras_state

Value updates for controls already published.

<extras_state>
  <extra>
    <object id="beam_level" value="72"/>
  </extra>
</extras_state>

streams and snapshots

Both wrap a flat list. Order is meaningful: the first entry is treated as the primary, higher quality source, and later entries as progressively lighter alternates.

<streams key="REQUEST_KEY">
  <stream url="rtsp://host:554/main" codec="h265" resolution="2560x1440"/>
  <stream url="rtsp://host:554/sub"  codec="h264" resolution="640x480"/>
</streams>

The key attribute is omitted when the publication was not prompted by a request. Snapshot documents follow the same pattern with snapshots and snapshot element names.

Behavior notes

The panel is replaced, never merged

Every setup document supersedes the last. Rebuild the whole thing from current state rather than trying to send a difference.

State updates do not resize the panel

A value update can change what a control reads, not what controls exist, and not the items inside a list. Changing the options themselves means sending a fresh setup document.

Nothing is acknowledged

These are notifications, so there is no delivery confirmation and no error path. A malformed document fails silently, which makes an empty or stale panel the normal symptom of a construction bug.

Confirm writes rather than assuming them

The interface shows the value the driver last published. If a write to the device fails and the driver stays quiet, the control keeps displaying a setting that was never applied.

Capability flags gate everything

Without the manifest flags the notifications are ignored. A panel that never appears is worth checking against the manifest before the code.

Invalidate on every addressing change

Address, ports, credentials, scheme, and reachability all feed URL construction. Any of them changing without an invalidation leaves consumers holding an address that no longer resolves.

The two halves have very different adoption

Extras are widely used across many proxy types and can be treated as well exercised. The dynamic URL flags are set by far fewer drivers, so that half is thinner and its edge cases are more likely to be untested.

Integration recipe

The minimum for a driver that wants both halves. Helper names here are placeholders for whatever the driver already has.

-- 1. publish the panel once capabilities are known
local function PublishExtras()
  local doc = {}

  doc[#doc + 1] = '<extras_setup><extra>'
  doc[#doc + 1] = '<section label="Illumination">'
  doc[#doc + 1] = string.format(
    '<object type="slider" id="%s" label="%s" command="%s"'
      .. ' value="%d" min="1" max="100"/>',
    "beam_level", "Brightness", "SET_BEAM_LEVEL", DeviceState.level)
  doc[#doc + 1] = '</section></extra></extras_setup>'

  C4:SendToProxy(CAMERA_BINDING, "EXTRAS_SETUP_CHANGED", {
    XML = table.concat(doc),
  }, "NOTIFY")
end

-- 2. handle the command the control declared
local function OnBeamLevel(tParams)
  local level = tonumber(tParams.LEVEL or tParams.VALUE)

  if not WriteLevelToDevice(level) then
    level = DeviceState.level  -- push the old value back
  else
    DeviceState.level = level
  end

  C4:SendToProxy(CAMERA_BINDING, "EXTRAS_STATE_CHANGED", {
    XML = string.format(
      '<extras_state><extra><object id="%s" value="%s"/></extra></extras_state>',
      "beam_level", tostring(level)),
  }, "NOTIFY")
end

-- 3. publish stream URLs
local function PublishStreams(key)
  local doc = {}

  if key then
    doc[#doc + 1] = string.format('<streams key="%s">', key)
  else
    doc[#doc + 1] = '<streams>'
  end

  for _, s in ipairs(BuildStreamList()) do
    doc[#doc + 1] = string.format(
      '<stream url="%s" codec="%s" resolution="%s"/>', s.url, s.codec, s.resolution)
  end

  doc[#doc + 1] = '</streams>'

  C4:SendToProxy(CAMERA_BINDING, "STREAM_URLS_READY", {
    KEY  = key,
    URLS = table.concat(doc),
  }, "NOTIFY")
end

-- 4. invalidate whenever addressing changes
local function OnAddressingChanged()
  C4:SendToProxy(CAMERA_BINDING, "DYNAMIC_URLS_CHANGED", {}, "NOTIFY")
end

Escaping is omitted above for brevity. Device supplied labels and stream names routinely contain characters that will break a document built by concatenation, so run every interpolated value through an XML escape before it reaches the string.