Trigger ISS SecurOS Event
Trigger a Analytics Event in ISS SecurOS VMS
Overview
This node triggers a event in ISS SecureOS VMS based on specified trigger conditions. The event can be used to generate alerts, review incidents and more.
Inputs & Outputs
- Inputs : 1, Media Format : Raw Video
- Outputs : 1, Media Format: Raw Video
- Output Metadata : None
Properties
Property | Description | Type | Default | Required |
---|---|---|---|---|
enabled | If false, this node will be disabled and will not send any alarms. Ex. false | boolean | true | Yes |
iss_ip | ISS SecurOS Server Hostname or IP | string | null | Yes |
iss_port | ISS SecurOS Server Port | string | 3000 | Yes |
iss_password | ISS SecurOS authentication password | string | null | Yes |
iss_camera_ids | Comma-separated list of Camera IDs from ISS SecurOS to associate with events. Leave blank to skip camera association. | string | null | No |
iss_action | Action to perform in ISS SecurOS. Options: Create event only (event ), Create event and record (event_and_record ), Create event, record and add subtitle (event_and_record_and_subtitle ) | enum | "event" | No |
record_duration | Recording duration in seconds when recording is enabled | integer | 5 | No |
event_source | Generate ISS SecurOS Events from events generated by other nodes or a Custom defined event. Options: Events from previous nodes (built_in ), Custom Event (custom ) | enum | built_in | Yes |
interval | Min. time between consecutive events | float | 0 | Yes |
trigger | Send an event when this condition evaluates to true. Required when event_source is "custom". | trigger-condition | null | Yes |
event_name | Event name that shows up in ISS SecurOS console. Accepts templates. Required when event_source is custom . | string | null | Yes |
event_description | Event description that shows up in ISS SecurOS console. Accepts templates. | text | null | No |
Event Description Template
event_description
uses Jinja2 Template Syntax for customizing the body of the Event description sent to Wave.
Variables available to the template:
Variable | Description |
---|---|
deployment_id | Deployment ID. |
deployment_name | Deployment Name. |
application_id | Application ID. |
application_name | Application Name. |
gateway_id | Gateway ID. |
gateway_name | Gateway Name. |
node_id | Node ID. |
meta.nodes | Node metadata. |
meta.objects | Object metadata. |
meta.custom_property | Custom properties inserted using a Custom function. |
Examples
Static text content:
Fire was detected in the parking lot
Insert description from an existing node:
{{ nodes.gpt1.fullframe.label }}
Metadata
Metadata Property | Description |
---|---|
None | None |
ISS SecurOS Configuration
Install nodejs modules in SecureOS
- Installing additional packages in Windows OS To install the package, do the following:
- Open a command line in administrator mode and navigate to the working directory of scripts (
C:\Program Files (x86)\ISS\SecurOS\bin64\node.js
). - Enter the command to run the npm installation script:
.\npm.bat install express selfsigned
- Installing additional packages in Linux OS To install the package, do the following:
- Open a command prompt and navigate to the working directory of scripts (
node.js
). - Enter the command to run the npm installation script:
sudo ./npm.sh install express selfsigned
Add nodejs script for lumeo events
Copy the following script into a new nodejs script within SecureOS configuration under Integration & Automation section. Remember to change the default auth token to some random value and provide that to this node's configuration in Lumeo.

// server.js
// Express webhook → Securos actions/events
// One server mode only: HTTP or HTTPS (toggle via USE_HTTPS=true)
// If HTTPS and cert/key not found, auto-generate a self-signed cert.
// Mapping: event_type="VCA_EVENT" always; event_name→comment; event_description→description.
const fs = require('fs');
const path = require('path');
const http = require('http');
const https = require('https');
const express = require('express');
const securos = require('securos');
// npm i selfsigned
const selfsigned = require('selfsigned');
const AUTH_TOKEN = process.env.AUTH_TOKEN || 'lumeo'; // required. Set it to a random string.
const USE_HTTPS = true;
const PORT = Number(3443);
// HTTPS cert paths (used only if USE_HTTPS=true)
const SSL_KEY = process.env.SSL_KEY || './key.pem';
const SSL_CERT = process.env.SSL_CERT || './cert.pem';
// ---- Securos connection ----
let core = null;
securos.connect((c) => {
core = c;
console.log('[securos] connected');
});
const app = express();
app.use(express.json());
// ---- helpers ----
function assertCoreReady(res) {
if (!core) {
res.status(503).json({ error: 'securos core not ready' });
return false;
}
return true;
}
function verifyAuth(req, res) {
if (!AUTH_TOKEN) {
res.status(500).json({ error: 'server misconfigured: AUTH_TOKEN missing' });
return false;
}
const h = req.get('authorization') || req.get('Authorization');
if (!h) return res.status(401).json({ error: 'missing Authorization header' });
if (h.startsWith('Bearer ')) {
const tok = h.slice(7).trim();
if (tok === AUTH_TOKEN) return true;
} else if (h.startsWith('Basic ')) {
try {
const decoded = Buffer.from(h.slice(6).trim(), 'base64').toString('utf8');
const [u, p] = decoded.split(':', 2);
if (u === AUTH_TOKEN || p === AUTH_TOKEN) return true;
} catch {}
}
res.status(401).json({ error: 'invalid Authorization' });
return false;
}
const toMs = (v, dflt) => {
const n = Number(v);
return Number.isFinite(n) && n >= 0 ? n*1000 : dflt;
};
const escHtml = (s = '') =>
String(s)
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", ''');
const asArray = (x) => (Array.isArray(x) ? x : x == null ? [] : [x]);
// ---- endpoints ----
app.get('/health', (_req, res) => res.json({ ok: true, coreReady: !!core, https: USE_HTTPS }));
app.post('/lumeoevent', async (req, res) => {
if (!assertCoreReady(res)) return;
if (!verifyAuth(req, res)) return;
const {
camera_id,
event_name, // → comment.description
event_description, // → comment.comment
event_duration, // seconds
trigger_recording, // boolean
add_subtitle, // boolean
visualization, // optional -> comment.visualization
cams // optional passthrough
} = req.body || {};
const camsList = asArray(camera_id).map(String);
if (camsList.length === 0) {
return res.status(400).json({ error: 'camera_id is required' });
}
const eventName = event_name ? String(event_name) : 'Lumeo Event';
const eventDescription = event_description ? String(event_description) : '';
const eventMs = toMs(event_duration, 10_000);
const secureOsCommentField = JSON.stringify({ comment: eventDescription,
description: eventName,
visualization: visualization });
const results = [];
for (const camId of camsList) {
const type = 'CAM';
const id = camId;
if (trigger_recording) {
try {
await core.doReact(type, id, 'REC', { hot_rec_time: eventMs });
setTimeout(() => { try { core.doReact(type, id, 'REC_STOP'); } catch {} }, eventMs + 500);
} catch (e) {
results.push({ camera_id: camId, step: 'REC', error: String(e?.message || e) });
}
}
if (add_subtitle) {
try {
await core.doReact(type, id, 'CLEAR_SUBTITLES');
const html = `<p style="color:red;">${escHtml(eventName || 'Event')}</p>` +
(eventDescription ? `<p>${escHtml(eventDescription)}</p>` : '');
await core.doReact(type, id, 'ADD_SUBTITLES', { command: html });
setTimeout(() => { try { core.doReact(type, id, 'CLEAR_SUBTITLES'); } catch {} }, eventMs);
} catch (e) {
results.push({ camera_id: camId, step: 'SUBTITLES', error: String(e?.message || e) });
}
}
try {
await core.sendEvent(type, id, 'VCA_EVENT', {
comment: secureOsCommentField,
//description,
cams
});
results.push({ camera_id: camId, step: 'EVENT', ok: true });
} catch (e) {
results.push({ camera_id: camId, step: 'EVENT', error: String(e?.message || e) });
}
}
res.json({ ok: true, results });
});
// ---- server bootstrap (one mode only) ----
function ensureSelfSignedCertSync(keyPath, certPath) {
const dir = path.dirname(path.resolve(keyPath));
if (!fs.existsSync(keyPath) || !fs.existsSync(certPath)) {
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
const attrs = [{ name: 'commonName', value: 'localhost' }];
const pems = selfsigned.generate(attrs, {
days: 365,
keySize: 2048,
algorithm: 'sha256',
extensions: [{ name: 'basicConstraints', cA: false }],
});
fs.writeFileSync(keyPath, pems.private);
fs.writeFileSync(certPath, pems.cert);
console.log(`[https] generated self-signed cert at ${keyPath}, ${certPath}`);
}
return {
key: fs.readFileSync(keyPath),
cert: fs.readFileSync(certPath),
};
}
if (USE_HTTPS) {
const tlsOpts = ensureSelfSignedCertSync(SSL_KEY, SSL_CERT);
https.createServer(tlsOpts, app).listen(PORT, () => {
console.log(`[https] listening on :${PORT}`);
});
} else {
http.createServer(app).listen(PORT, () => {
console.log(`[http] listening on :${PORT}`);
});
}
/*
ENV:
# HTTP
AUTH_TOKEN=shh PORT=3000 node server.js
# HTTPS (auto self-signed if missing)
USE_HTTPS=true AUTH_TOKEN=shh SSL_KEY=./key.pem SSL_CERT=./cert.pem PORT=3443 node server.js
Test (Bearer):
curl -k -X POST https://localhost:3443/lumeoevent \
-H 'Authorization: Bearer shh' \
-H 'Content-Type: application/json' \
-d '{"camera_id":["1","2"],"event_name":"Intrusion","event_description":"Person at gate","trigger_recording":true,"recording_duration":12000,"add_subtitle":true,"subtitle_duration":4000,"visualization":"rect:10,10,30,40;color:255,0,0"}'
Note: Basic also accepted (token as username or password).
*/
Updated about 5 hours ago