Devices and VAPIX
Every plugin works on the devices OADM manages. The server keeps the device table and the stored passwords. A plugin
sees each device as a read-only IDeviceInfo and talks to it through an IVapixClient that is already logged in.
The device: IDeviceInfo
Section titled “The device: IDeviceInfo”| Member | Content |
|---|---|
Id |
OADM’s id of the device (Guid). Use it for every call and as the key of your own data |
Serial |
The MAC address, upper case without separators (B8A44F631339) |
Address, HostName |
The address OADM reaches it at (IP or DNS name) and its host name |
Model, FirmwareVersion |
Product number (P3265-V) and AXIS OS version (12.11.77) |
Status |
Ok, Unreachable, CredentialsRequired, PasswordNotSet, CertificateChanged, Unknown |
Category, HasVideo |
Camera, encoder, speaker, intercom, I/O module, …; HasVideo for cameras, encoders and intercoms |
Apis |
The VAPIX API list of the last full refresh, see API checks |
Tags |
The tags the user gave the device (“Building A”) |
CredentialUserName |
The account OADM logs in with. Never remove or demote it. Never the password |
DhcpEnabled, HttpsEnabled, Dot1xEnabled |
Network facts from the last full refresh; null while unknown |
CertNotAfterUtc, CertTrustName |
The HTTPS certificate’s end date and its rating |
The server polls every device’s status every 60 s and reads everything else in a full refresh every 10 minutes, after
every task and when a device comes back. IDeviceInfo is that cached picture: reading it costs nothing and never
contacts the device.
Status first
Section titled “Status first”Before a plugin sends anything, it checks the status. OADM never sends requests to a device it cannot log in to:
if (DeviceMessages.Refusal(device.Status, refuseUnreachable: true) is { } refusal){ // "Certificate changed. Remove the device and add it again ...", "Credentials required - ...", ... return refusal; // show it in the row or fail the task with it; send nothing}DeviceMessages has every text OADM shows for a status or an HTTP error (StatusText, ForHttpStatus, TimedOut,
Unreachable(detail)), so your plugin says it the same way as the rest of the app.
Getting devices
Section titled “Getting devices”On the server
Section titled “On the server”| Where | How |
|---|---|
| Task plugin | ExecuteAsync(ctx, device, payload, ct) gets its device; ctx.Vapix is logged in to it |
| Task query | QueryAsync(ctx, device, ...) the same; ctx.Devices lists all managed devices |
| Core plugin | ctx.Devices.ListAsync(ct) and ctx.Devices.FindAsync(id, ct); ctx.Vapix.CreateAsync(id, ct) for a client |
| Action | ctx.Vapix for the target device when NeedsDevice is true |
A core plugin that reads many devices does it with bounded parallelism, a timeout per device and the status check first. This reads the AXIS OS version of every video device, 16 at a time:
public sealed record FirmwareRow(Guid DeviceId, string Address, string? Version, string? Error);
public static async Task<IReadOnlyList<FirmwareRow>> ReadFirmwareAsync(ICorePluginContext ctx, CancellationToken ct){ var devices = await ctx.Devices.ListAsync(ct); var rows = new ConcurrentBag<FirmwareRow>();
await Parallel.ForEachAsync( devices.Where(d => d.HasVideo), new ParallelOptions { MaxDegreeOfParallelism = 16, CancellationToken = ct }, async (device, token) => { // Never send a request to a device OADM cannot log in to. if (DeviceMessages.Refusal(device.Status, refuseUnreachable: true) is { } refusal) { rows.Add(new FirmwareRow(device.Id, device.Address, null, refusal)); return; }
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(token); timeout.CancelAfter(TimeSpan.FromSeconds(5)); try { var vapix = await ctx.Vapix.CreateAsync(device.Id, timeout.Token); // cached by the host: never dispose it var info = await vapix.GetBasicDeviceInfoAsync(timeout.Token); rows.Add(new FirmwareRow(device.Id, device.Address, info.Version, null)); } catch (Exception ex) when (ex is not OperationCanceledException || !token.IsCancellationRequested) { rows.Add(new FirmwareRow(device.Id, device.Address, null, ex is OperationCanceledException ? DeviceMessages.TimedOut : ex.Message)); } });
return [.. rows];}For a page that shows one row per device and reads them when the user asks, use the shared DeviceSweep and
SweepRows in plugins/Shared/ instead (one sweep at a time, rows pushed in batches, updated in place). The Image
Health Dashboard and Edge storage are built on them.
In the client
Section titled “In the client”The client never talks to devices, but it knows them all: it keeps a live copy of the device table.
| Where | Devices |
|---|---|
| Task dialog | ShowAsync(ctx, devices, owner): the selected devices; ctx.AllDevices: every device |
| Core plugin page | ctx.Devices (all), ctx.SelectedDevices (the Devices page selection), event ctx.DevicesChanged |
| Toolbar plugin | ctx.Devices, ctx.SelectedDevices, events DevicesChanged and SelectionChanged |
Never cache the selection: read it when you need it. To let the user pick devices, use the shared
DevicePickerWindow (search, tags, Select all, 5,000 devices), see UI kit.
Talking to a device: IVapixClient
Section titled “Talking to a device: IVapixClient”| Member | Does |
|---|---|
GetBasicDeviceInfoAsync |
basicdeviceinfo.cgi: serial, product number, AXIS OS, product type, SoC |
ListParametersAsync(groups) |
param.cgi?action=list; keys without the root. prefix |
GetApiListAsync |
A fresh API list (apidiscovery.cgi); on AXIS OS 8.40 built from parameters |
GetVideoSourcesAsync |
The video channels with their resolutions, as the live view offers them |
RestartAsync |
restart.cgi |
SendAsync(request) |
Any request, relative to the device |
ConnectWebSocketAsync(path, keepAlive) |
A WebSocket, logged in with a one-time session token |
The client uses the stored login, scheme and pinned certificate. It answers Digest over HTTP and Basic or Digest over HTTPS. Your plugin never sees the password.
Any request with SendAsync
Section titled “Any request with SendAsync”using var request = new HttpRequestMessage(HttpMethod.Post, "/axis-cgi/io/portmanagement.cgi"){ Content = new StringContent("""{"apiVersion":"1.0","method":"getPorts"}""", Encoding.UTF8, "application/json"),};using var response = await vapix.SendAsync(request, ct);if (!response.IsSuccessStatusCode){ throw new InvalidOperationException( DeviceMessages.ForHttpStatus((int)response.StatusCode) ?? DeviceMessages.ServerError((int)response.StatusCode));}
var json = await response.Content.ReadAsStringAsync(ct); // JSON APIs answer errors with HTTP 200: check "error"- Paths only. Pass a path relative to the device. A URI with another host, port or scheme,
//host, or a backslash is refused before anything is sent. - Values in the body. Send arguments of writes in the POST body, never in the URL (URLs end up in logs).
- Timeouts. 15 s by default. For an upload set
request.Options.Set(VapixRequestOptions.Timeout, TimeSpan.FromMinutes(20))and send the file asStreamContent; bodies are streamed, never buffered. - Answer size. At most 16 MB is read. For large downloads set
VapixRequestOptions.StreamResponseand copyresponse.Content.ReadAsStreamAsync(ct)to a file with your own size limit and token. - XML. Parse device XML only with
DeviceXml.Parse/DeviceXml.ParseElement(1 MB, no DTD), neverXDocument.Parse. - Errors. Axis devices often answer errors with HTTP 200 and the error in the body (
# Error,"error",<GeneralError>). Check the body.
API checks
Section titled “API checks”Apis lists the VAPIX APIs a device offers, with versions. The rule: a different major version is a different API,
a higher minor version within the same major is compatible.
device.Apis.Supports("network-settings", "1.12"); // CanRun: cached list, no request(await ctx.Vapix.GetApiListAsync(ct)).Require("network-settings", "1.12"); // before the first write: a fresh list, throws // DeviceNotCompatibleException "... Nothing was changed."device.Apis.FindApi("fwmgr")?.Version; // the highest listed versionThe API list is read in the full refresh. A device that was just added may have none yet: then
TaskSupportReasons.NeedsApi(...) tells the user to refresh it.
WebSockets
Section titled “WebSockets”using var socket = await vapix.ConnectWebSocketAsync("/vapix/ws-data-stream?sources=events", TimeSpan.FromSeconds(15), ct);ws for HTTP devices, wss with the pinned certificate for HTTPS devices. The socket pings at the interval and aborts when the device stops answering. Dispose it, and reconnect with a backoff when it breaks. Used by Device triggers.
Event streams (RTSP)
Section titled “Event streams (RTSP)”await using var source = await ctx.EventStreams!.OpenAsync(deviceId, ct);await foreach (var document in source.ReadAsync(ct)){ var xml = DeviceXml.Parse(document.Xml); // one tt:MetadataStream document}The server opens the RTSP event stream with the stored login and the device’s RTSP port. Failures are
DeviceStreamException with Error (unreachable: retry; unauthorized, not supported: give up). Used by the Metadata
Monitor.
GetVideoSourcesAsync returns the channels (view areas, sensors, encoder inputs) with their resolutions. A page that
shows live video uses the host’s live video component (ICorePluginClientContext.LiveVideo), see
Core plugins and pages.
Changing what OADM knows about a device
Section titled “Changing what OADM knows about a device”A task that changes how OADM reaches the device tells the server, which checks the device first:
| After | Call |
|---|---|
| Changing the password of OADM’s own account | ctx.UpdateCredentialsAsync(user, password, ct), then use ctx.Vapix again |
| A factory default | ctx.MarkCredentialsInvalid() |
| Giving the device a new address | ctx.CreateClientForAsync(newAddress, ct) to reach it there, then ctx.UpdateDeviceAddressAsync(newAddress, ct) |
| A new HTTPS certificate, or HTTPS off | ctx.UpdateDeviceTlsAsync("https", fingerprint, ct) or ("http", null, ct) |
The server checks the serial number at the new address or with the new certificate. If it does not match, it throws
DeviceIdentityException and keeps the record as it was.
Device safety
Section titled “Device safety”A plugin must never break a device or lock OADM out of it. Before you write to devices, read Task plugins: device safety.
Reference
Section titled “Reference”Every member with its signature in the SDK reference: IDeviceInfo, DeviceStatus, DeviceMessages, IDeviceRepository, IVapixClient, IVapixClientFactory, DeviceApiExtensions, VapixRequestOptions, DeviceXml, IDeviceEventStreams.