Skip to content

Instantly share code, notes, and snippets.

@curtishall
Last active March 15, 2025 20:56
Show Gist options
  • Save curtishall/a9e66351bfc8213a749daf3876b86e88 to your computer and use it in GitHub Desktop.
Save curtishall/a9e66351bfc8213a749daf3876b86e88 to your computer and use it in GitHub Desktop.
nodejs onvif script to return available event types.
/**
* bc-check-onvif.js
*
* This script reports:
* - MyRuleDetector rules (e.g. People Detect, Vehicle Detect, Dog Cat Detect)
* - RuleEngine events such as CellMotionDetector/Motion
* - VideoSource events such as MotionAlarm
*
* Usage:
* node bc-check-onvif.js <ip> <port> <username> <password>
*
* Example:
* node bc-check-onvif.js 192.168.87.151 80 admin admin
*/
if (process.argv.length < 6) {
console.log("Usage: node bc-check-onvif.js <ip> <port> <username> <password>");
process.exit(1);
}
const HOSTNAME = process.argv[2];
const PORT = process.argv[3];
const USERNAME = process.argv[4];
const PASSWORD = process.argv[5];
const EventMethodTypes = { PULL: "pull", SUBSCRIBE: "subscribe" };
// For this example, we use PullPoint events.
let EVENT_MODE = EventMethodTypes.PULL;
let Cam = require('/usr/share/nodejs/onvif').Cam;
let cam_obj = null;
let flow = require('nimble');
let ruleSet = new Set();
// Helper: Remove namespace prefixes from a topic string.
// E.g., "tns1:VideoSource" becomes "VideoSource"
function stripNamespaces(topic) {
if (!topic) return "";
return topic.split('/').map(function(part) {
return part.split(':').pop();
}).join('/');
}
/**
* processTopic: Examines the full topic string and aggregates events of interest.
*
* It does the following:
* 1. If the topic includes "MyRuleDetector", it extracts the rule name (the element immediately following)
* and normalizes it (inserting a space before "Detect"). FaceDetect events are skipped.
* 2. Otherwise, if the topic contains "CellMotionDetector" (under RuleEngine), it extracts that branch
* and stores it in the form "CellMotionDetector: <next-part>".
* 3. Similarly, if the topic contains "VideoSource" and "MotionAlarm", it stores it as "VideoSource: MotionAlarm".
*/
function processTopic(topic) {
if (!topic) return;
const lower = topic.toLowerCase();
// Case 1: MyRuleDetector events.
if (lower.includes('myruledetector')) {
let parts = topic.split('/').filter(Boolean);
let idx = parts.findIndex(part => part.toLowerCase() === 'myruledetector');
if (idx >= 0 && parts.length > idx + 1) {
let rule = parts[idx + 1];
rule = rule.replace(/detect/i, ' Detect');
// Skip FaceDetect events if desired.
if (rule.toLowerCase().includes('face')) return;
ruleSet.add(rule);
}
}
// Case 2: Other RuleEngine events (e.g. CellMotionDetector)
else if (lower.includes('ruleengine') && lower.includes('cellmotiondetector')) {
let parts = topic.split('/').filter(Boolean);
let idx = parts.findIndex(part => part.toLowerCase() === 'cellmotiondetector');
if (idx >= 0 && parts.length > idx + 1) {
let rule = parts[idx] + ': ' + parts[idx + 1];
ruleSet.add(rule);
}
}
// Case 3: VideoSource events (e.g. MotionAlarm)
else if (lower.includes('videosource') && lower.includes('motionalarm')) {
let parts = topic.split('/').filter(Boolean);
let idx = parts.findIndex(part => part.toLowerCase() === 'videosource');
if (idx >= 0 && parts.length > idx + 1) {
let rule = parts[idx] + ': ' + parts[idx + 1];
ruleSet.add(rule);
}
}
}
// Event handler for incoming events.
function ReceivedEvent(camMessage, xml) {
if (camMessage.topic && camMessage.topic._) {
let topic = camMessage.topic._;
console.log('Received Event Topic:', topic);
let cleanTopic = stripNamespaces(topic);
processTopic(cleanTopic);
} else {
console.log('Received event with no topic.');
}
}
console.log("*******************************************************************************");
console.log("** This script subscribes for events and aggregates supported rules");
console.log("** Usage: node bc-check-onvif.js <ip> <port> <username> <password>");
console.log("*******************************************************************************");
new Cam({
hostname: HOSTNAME,
username: USERNAME,
password: PASSWORD,
port: PORT,
timeout: 10000,
preserveAddress: true
}, function CamFunc(err) {
if (err) {
console.log("Error connecting to device:", err);
process.exit(1);
}
console.log('Connected to ONVIF Device');
cam_obj = this;
flow.series([
function(callback) {
cam_obj.getDeviceInformation(function(err, info, xml) {
if (!err) {
console.log('Manufacturer ' + info.manufacturer);
console.log('Model ' + info.model);
console.log('Firmware ' + info.firmwareVersion);
console.log('Serial Number ' + info.serialNumber);
}
callback();
});
},
function(callback) {
cam_obj.getSystemDateAndTime(function(err, date, xml) {
if (!err) { console.log('Device Time ' + date); }
callback();
});
},
function(callback) {
cam_obj.getCapabilities(function(err, data, xml) {
if (err) { console.log(err); }
callback();
});
},
function(callback) {
cam_obj.getEventProperties(function(err, data, xml) {
if (err) {
console.log(err);
} else {
console.log("Event properties retrieved.");
}
callback();
});
}
], function() {
// Listen for events via PullPoint subscription.
cam_obj.on('event', function(camMessage, xml) {
ReceivedEvent(camMessage, xml);
});
console.log("Listening for events for 20 seconds...");
// Prevent the process from exiting immediately.
process.stdin.resume();
setTimeout(function() {
console.log("\nPolling complete.\n");
console.log("This camera supports the following rules:\n");
if (ruleSet.size === 0) {
console.log("No rules detected.");
} else {
Array.from(ruleSet).sort().forEach(function(rule) {
console.log(rule);
});
}
process.exit(0);
}, 20000);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment