Skip to content

Driver lifecycle

Drivers come in two flavours. This guide covers both.

TypeEntry pointsPurpose
GENERICvalidate() + get_status()Scheduled polling, publishes variables / metrics / tables
CONFIGURATION_MANAGEMENTvalidate() + backup() + restore(configuration)Scheduled config snapshots + on-demand restore

The driver type is picked when the driver is uploaded — a dropdown in the custom-driver editor. You can’t change it afterward without deleting the driver.

Most drivers are GENERIC. If you’re writing backup / restore, jump to Configuration management.


A GENERIC driver is a script with two required entry points: validate and get_status. The collector calls them at different moments with the same sandboxed D namespace available.

Runs once, the first time the driver is attached to a device.

function validate() {
D.device.ping({ count: 1 }, function (result, error) {
if (error || !result || result.packet_loss === 100) {
return D.failure(D.errorType.RESOURCE_UNAVAILABLE);
}
D.success();
});
}

Use validate to check that:

  • The device is reachable over the protocol the driver uses.
  • Credentials (if any) work.
  • The device model/firmware matches what the driver expects (read a known endpoint, check for a specific field).

validate must end with exactly one call to D.success() (no arguments) or D.failure(D.errorType.X). Do not return variables from validate — the portal does not persist them.

Runs on every schedule tick — the cadence is configured in the Domotz portal (default 5 minutes, common values 1 / 5 / 15 / 60 minutes).

function get_status() {
D.device.http.get({ url: '/api/metrics' }, function (error, response, body) {
if (error) return D.failure(D.errorType.RESOURCE_UNAVAILABLE);
var data = JSON.parse(body);
var temp = D.createVariable('temp', 'Temperature', data.temperature, 'C', D.valueType.NUMBER);
D.success([temp]);
});
}

get_status must end with exactly one call to one of:

  • D.success() — nothing to report this tick, but the driver is healthy.
  • D.success([variables]) — publish a set of variables.
  • D.success(table) — publish a table.
  • D.success([variables], table) — publish both.
  • D.failure(D.errorType.X) — report a problem.

CONFIGURATION_MANAGEMENT (CM) drivers are built for one job: capturing a snapshot of the device’s configuration and restoring it on demand. They show up in the portal as a separate Backup / Restore surface.

Same contract as a GENERIC driver — check the driver can reach the device and authenticate. End with D.success() or D.failure(D.errorType.X).

Called on the schedule configured in the portal. Fetch the device’s config, wrap it in a ConfigurationBackup, validate via D.createBackup(...), and hand back via D.success(...).

function backup() {
D.device.sendSSHCommand({ command: 'show running-config' }, function (running, error) {
if (error) return D.failure(D.errorType.RESOURCE_UNAVAILABLE);
D.device.sendSSHCommand({ command: 'show startup-config' }, function (startup) {
var validated = D.createBackup({
label: 'Full configuration',
running: running,
startup: startup || null,
ignoredLines: ['^!.*timestamp', '^Building configuration']
});
D.success(validated);
});
});
}

The ConfigurationBackup shape:

  • label — human-readable snapshot name (string).
  • running — the running config as a string. Required.
  • startup — the startup config as a string, or null if the device has only one config.
  • ignoredLines — array of regular expressions (as strings). The portal diff view hides lines matching these when comparing snapshots — useful for stripping volatile fields (timestamps, counters).

Hard size limits: agentDriverSettings.max_config_backup_size bytes for each of running and startup, and max_ignored_lines entries.

User-triggered RPC. The portal invokes this when the user picks a snapshot and clicks Restore. The configuration argument is a ConfigurationRestore:

{
content: string, // the full configuration text to push
label: string, // which snapshot the user selected
timestamp: string, // ISO-8601 of when the snapshot was taken
source: 'running' | 'startup' // which half of the original backup this is
}

Declare restore with the @remote_procedure marker so the portal knows it can trigger it:

/**
* @remote_procedure
* @label Restore
* @documentation Restore device configuration
* @param {ConfigurationRestore} configuration
*/
function restore(configuration) {
var cmds = configuration.content.split('\n');
D.device.sendSSHCommands({
commands: ['configure terminal'].concat(cmds).concat(['end', 'write memory']),
prompt: '#'
}, function (outputs, error) {
if (error) return D.failure(D.errorType.RESOURCE_UNAVAILABLE);
D.success();
});
}

@label is the button label in the portal UI. @documentation is the tooltip / help text shown next to the button.

Restore is often a privileged operation — you may need to pass username / password overrides specifically for the restore path if the CDM credentials are read-only.

/**
* @description Enable password
* @type SECRET_TEXT
*/
var enablePassword;
function validate() {
D.device.sendSSHCommand({ command: 'show version' }, function (out, error) {
if (error) return D.failure(D.errorType.RESOURCE_UNAVAILABLE);
D.success();
});
}
function backup() {
D.device.sendSSHCommand({ command: 'show running-config' }, function (running, error) {
if (error) return D.failure(D.errorType.RESOURCE_UNAVAILABLE);
D.success(D.createBackup({
label: 'Running config',
running: running
}));
});
}
/**
* @remote_procedure
* @label Restore
* @documentation Replace the device's running config with a saved snapshot.
* @param {ConfigurationRestore} configuration
*/
function restore(configuration) {
D.device.sendSSHCommands({
commands: ['configure terminal', configuration.content, 'end', 'write memory'],
prompt: '#'
}, function (_out, error) {
if (error) return D.failure(D.errorType.RESOURCE_UNAVAILABLE);
D.success();
});
}

When the portal diffs two snapshots to show the user what changed, volatile fields (config timestamps, uptime counters written into the running config) create noise. Put regex patterns that match those lines in ignoredLines and the diff view drops them:

ignoredLines: [
'^!Last configuration change at',
'^!NVRAM config last updated at',
'^Current configuration : \\d+ bytes'
]

Match patterns are JavaScript regex strings (not literals), anchored with ^ at the start of the line.


Pick the errorType that matches reality. The portal uses this to distinguish transient glitches from hard breakages in dashboards.

D.errorType.*When to use
RESOURCE_UNAVAILABLEDevice is unreachable or the endpoint times out.
AUTHENTICATION_ERRORCredentials rejected (401/403, bad SSH key, SNMP community wrong).
PARSING_ERRORResponse returned but its structure is not what the driver expects.
TIMEOUT_ERROROperation took longer than its timeout.
GENERIC_ERRORAnything else — prefer one of the above when it fits.

See D.errorType in the API reference for the full list.

The collector runs get_status / backup on an interval set per-driver in the portal. Keep this in mind:

  • Each run is independent. Don’t assume state survives. If you need to accumulate values across runs, use tables with stable row UIDs — the portal deduplicates by UID and keeps history.
  • Runs are killed if they exceed the per-run wall-clock limit. Never call any D.* function without eventually reaching a D.success / D.failure. Un-exited drivers are killed and counted as failures.
  • Concurrent runs can happen if the previous tick has not finished. Design your driver so that a late callback cannot clobber the next tick’s data — don’t stash results in module-level variables.
function get_status() {
var username = D.device.username();
var password = D.device.password();
if (!username || !password) {
return D.failure(D.errorType.AUTHENTICATION_ERROR);
}
// ...
}
function get_status() {
var pending = 2;
var results = {};
function done() {
pending -= 1;
if (pending === 0) {
D.success([
D.createVariable('temp', 'Temperature', results.temp, 'C', D.valueType.NUMBER),
D.createVariable('load', 'Load', results.load, '%', D.valueType.NUMBER),
]);
}
}
D.device.sendSSHCommand({ command: 'sensors -u' }, function (out) { results.temp = parseTemp(out); done(); });
D.device.sendSSHCommand({ command: 'uptime' }, function (out) { results.load = parseLoad(out); done(); });
}

For anything more complex, reach for D.q (the sandbox’s bundled promise library) to coordinate parallel work.

Both GENERIC and CM drivers can declare portal-visible parameters — values the user fills in when uploading or assigning the driver. Use a top-of-file JSDoc block with @type:

/**
* @description Host password
* @type SECRET_TEXT
*/
var password;
/**
* @description Poll interval seconds
* @type NUMBER
*/
var interval;
/**
* @description Device model
* @type TEXT
*/
var model;

Supported types:

@typePortal UIWhat the driver sees
TEXTPlain text fieldString
SECRET_TEXTMasked password field, never displayed once savedString
NUMBERNumber inputNumber

The declared variables are injected into the driver’s global scope before validate / get_status / backup / restore run. SECRET_TEXT values are encrypted at rest and redacted from logs.

Read them directly or via D.getParameter('name').