ts-onvif
Version:
Client to ONVIF devices
613 lines • 27.4 kB
JavaScript
"use strict";
/**
* PTZ ver20 module
* @author Andrew D.Laptev <a.d.laptev@gmail.com>
* @see https://www.onvif.org/onvif/ver20/ptz/wsdl/ptz.wsdl
* @see https://www.onvif.org/specs/srv/ptz/ONVIF-PTZ-Service-Spec-v1712.pdf
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const service_1 = __importDefault(require("./service"));
const toOnvifXMLSchemaObject_1 = require("./utils/toOnvifXMLSchemaObject");
/**
* PTZ methods
*/
class PTZ extends service_1.default {
#nodes = {};
get nodes() {
return this.#nodes;
}
#configurations = {};
get configurations() {
return this.#configurations;
}
#presets = {};
get presets() {
return this.#presets;
}
constructor(onvif) {
super(onvif, 'PTZ');
}
/**
* Returns the capabilities of the PTZ service. The result is returned in a typed answer.
*/
async getServiceCapabilities() {
const response = await this.request({
GetServiceCapabilities: {},
});
return response.getServiceCapabilitiesResponse?.capabilities ?? [];
}
/**
* Get a specific PTZ Node identified by a reference token or a name.
* @param nodeToken
*/
async getNode({ nodeToken }) {
const response = await this.request({
GetNode: {
NodeToken: nodeToken,
},
});
return response.getNodeResponse?.PTZNode;
}
/**
* Returns an object of the existing PTZ Nodes on the device: node name -> PTZNode.
* Use this function to get maximum number of presets, ranges of admitted values for x, y, zoom, iris, focus
*/
async getNodesExtended() {
const response = await this.request({ GetNodes: {} }, { array: ['getNodesResponse'] });
this.#nodes = {};
response.getNodesResponse.forEach((ptzNode) => {
const node = ptzNode.PTZNode;
this.#nodes[node.token] = node;
});
return this.#nodes;
}
/**
* Returns list of the existing PTZ Nodes on the device
* Use this function to get maximum number of presets, ranges of admitted values for x, y, zoom, iris, focus
*/
async getNodes() {
return this.getNodesExtended().then((nodesObject) => Object.values(nodesObject));
}
/**
* Get an object with all the existing PTZConfigurations from the device
*/
async getConfigurationsExtended() {
const response = await this.request({ GetConfigurations: {} }, { array: ['PTZConfiguration'] });
this.#configurations = {};
response.getConfigurationsResponse.PTZConfiguration.forEach((configuration) => {
this.#configurations[configuration.token] = configuration;
});
return this.#configurations;
}
/**
* Get an array with all the existing PTZConfigurations from the device
*/
async getConfigurations() {
return this.getConfigurationsExtended().then((configurationsObject) => Object.values(configurationsObject));
}
/**
* Get a specific PTZconfiguration from the device, identified by its reference token or name.
*
* The default Position/Translation/Velocity Spaces are introduced to allow NVCs sending move requests without
* the need to specify a certain coordinate system. The default Speeds are introduced to control the speed of move
* requests (absolute, relative, preset), where no explicit speed has been set.
*
* The allowed pan and tilt range for Pan/Tilt Limits is defined by a two-dimensional space range that is mapped
* to a specific Absolute Pan/Tilt Position Space. At least one Pan/Tilt Position Space is required by the PTZNode
* to support Pan/Tilt limits. The limits apply to all supported absolute, relative and continuous Pan/Tilt movements.
* The limits shall be checked within the coordinate system for which the limits have been specified. That means that
* even if movements are specified in a different coordinate system, the requested movements shall be transformed
* to the coordinate system of the limits where the limits can be checked. When a relative or continuous movements
* is specified, which would leave the specified limits, the PTZ unit has to move along the specified limits.
* The Zoom Limits have to be interpreted accordingly.
* @param options
*/
async getConfiguration(options) {
const response = await this.request({
GetConfiguration: {
PTZConfigurationToken: options.PTZConfigurationToken,
},
});
return response.getConfigurationResponse.PTZConfiguration;
}
/**
* Set/update a existing PTZConfiguration on the device.
* @param options
*/
async setConfiguration(options) {
await this.request({
SetConfiguration: {
PTZConfiguration: {
$: {
token: options.PTZConfiguration.token,
MoveRamp: options.PTZConfiguration.moveRamp,
PresetRamp: options.PTZConfiguration.presetRamp,
PresetTourRamp: options.PTZConfiguration.presetTourRamp,
},
Name: options.PTZConfiguration.name,
UseCount: options.PTZConfiguration.useCount,
NodeToken: options.PTZConfiguration.nodeToken,
DefaultAbsolutePantTiltPositionSpace: options.PTZConfiguration.defaultAbsolutePantTiltPositionSpace,
DefaultAbsoluteZoomPositionSpace: options.PTZConfiguration.defaultAbsoluteZoomPositionSpace,
DefaultRelativePanTiltTranslationSpace: options.PTZConfiguration.defaultRelativePanTiltTranslationSpace,
DefaultRelativeZoomTranslationSpace: options.PTZConfiguration.defaultRelativeZoomTranslationSpace,
DefaultContinuousPanTiltVelocitySpace: options.PTZConfiguration.defaultContinuousPanTiltVelocitySpace,
DefaultContinuousZoomVelocitySpace: options.PTZConfiguration.defaultContinuousZoomVelocitySpace,
DefaultPTZSpeed: PTZ.PTZVectorToXML(options.PTZConfiguration.defaultPTZSpeed),
DefaultPTZTimeout: options.PTZConfiguration.defaultPTZTimeout,
...(options.PTZConfiguration.panTiltLimits && {
PanTiltLimits: {
Range: {
URI: options.PTZConfiguration.panTiltLimits.range.URI,
XRange: {
Min: options.PTZConfiguration.panTiltLimits.range.XRange.min,
Max: options.PTZConfiguration.panTiltLimits.range.XRange.max,
},
YRange: {
Min: options.PTZConfiguration.panTiltLimits.range.YRange.min,
Max: options.PTZConfiguration.panTiltLimits.range.YRange.max,
},
},
},
}),
...(options.PTZConfiguration.zoomLimits && {
ZoomLimits: {
Range: {
URI: options.PTZConfiguration.zoomLimits.range.URI,
XRange: {
Min: options.PTZConfiguration.zoomLimits.range.XRange.min,
Max: options.PTZConfiguration.zoomLimits.range.XRange.max,
},
},
},
}),
Extension: options.PTZConfiguration.extension,
},
ForcePersistence: options.forcePersistence,
},
});
}
/**
* List supported coordinate systems including their range limitations.
* Therefore, the options MAY differ depending on whether the PTZ Configuration is assigned to a Profile containing
* a Video Source Configuration. In that case, the options may additionally contain coordinate systems referring to
* the image coordinate system described by the Video Source Configuration. If the PTZ Node supports continuous
* movements, it shall return a Timeout Range within which Timeouts are accepted by the PTZ Node
* @param options
* @param options.configurationToken Token of an existing configuration that the options are intended for
*/
async getConfigurationOptions({ configurationToken }) {
const response = await this.request({
GetConfigurationOptions: { ConfigurationToken: configurationToken },
});
return response.getConfigurationOptionsResponse.PTZConfigurationOptions;
}
/**
* Operation to get all available PTZConfigurations that can be added to the referenced media profile.
* @param options
*/
async getCompatibleConfigurations({ profileToken = this.onvif.activeSource.profileToken, } = {}) {
const response = await this.request({ GetCompatibleConfigurations: { ProfileToken: profileToken } }, { array: ['PTZConfiguration'] });
return response.getCompatibleConfigurationsResponse.PTZConfiguration ?? [];
}
/**
* Operation to send auxiliary commands to the PTZ device mapped by the PTZNode in the selected profile.
* The operation is supported if the AuxiliarySupported element of the PTZNode is true
* @param options
*/
async sendAuxiliaryCommand(options) {
const response = await this.request({
SendAuxiliaryCommand: {
ProfileToken: options.profileToken ?? this.onvif.activeSource.profileToken,
AuxiliaryData: options.auxiliaryData,
},
});
const auxiliaryResponse = response.sendAuxiliaryCommandResponse.auxiliaryResponse;
if (Array.isArray(auxiliaryResponse)) {
return auxiliaryResponse[0] ?? '';
}
return auxiliaryResponse ?? '';
}
/**
* Operation to request all PTZ presets with token names as an object for the PTZNode in the selected profile.
* The operation is supported if there is support for at least on PTZ preset by the PTZNode.
*/
async getPresetsExtended({ profileToken } = { profileToken: this.onvif.activeSource.profileToken }) {
const response = await this.request({ GetPresets: { ProfileToken: profileToken } }, { array: ['preset'] });
this.#presets = {};
const result = response.getPresetsResponse.preset;
result.forEach((preset) => {
this.#presets[preset.token] = preset;
});
return this.#presets;
}
/**
* Operation to request a list of all PTZ presets for the PTZNode in the selected profile.
* The operation is supported if there is support for at least on PTZ preset by the PTZNode.
*/
async getPresets({ profileToken } = { profileToken: this.onvif.activeSource.profileToken }) {
return this.getPresetsExtended({ profileToken }).then((result) => Object.values(result));
}
/**
* Operation to request all existing preset tours in the selected profile.
* @param options
*/
async getPresetTours({ profileToken } = { profileToken: this.onvif.activeSource.profileToken }) {
const response = await this.request({ GetPresetTours: { ProfileToken: profileToken } }, { array: ['presetTour'] });
return response.getPresetToursResponse.presetTour ?? [];
}
/**
* Operation to request a specific preset tour in the selected profile.
* @param options
*/
async getPresetTour({ profileToken = this.onvif.activeSource.profileToken, presetTourToken, }) {
const response = await this.request({
GetPresetTour: {
ProfileToken: profileToken,
PresetTourToken: presetTourToken,
},
});
return response.getPresetTourResponse.presetTour;
}
/**
* Operation to request available options to create and modify preset tours.
* @param options
*/
async getPresetTourOptions({ profileToken = this.onvif.activeSource.profileToken, presetTourToken, } = {}) {
const response = await this.request({
GetPresetTourOptions: {
ProfileToken: profileToken,
PresetTourToken: presetTourToken,
},
});
return response.getPresetTourOptionsResponse.options;
}
/**
* Operation to create a new preset tour for the selected profile.
* @param options
*/
async createPresetTour({ profileToken = this.onvif.activeSource.profileToken, } = {}) {
const response = await this.request({
CreatePresetTour: {
ProfileToken: profileToken,
},
});
return response.createPresetTourResponse.presetTourToken;
}
/**
* Operation to modify the specified preset tour for the selected profile.
* @param options
*/
async modifyPresetTour({ profileToken = this.onvif.activeSource.profileToken, presetTour, }) {
await this.request({
ModifyPresetTour: {
ProfileToken: profileToken,
PresetTour: PTZ.presetTourToXML(presetTour),
},
});
}
/**
* Operation to perform an operation on the specified preset tour.
* @param options
*/
async operatePresetTour({ profileToken = this.onvif.activeSource.profileToken, presetTourToken, operation, }) {
await this.request({
OperatePresetTour: {
ProfileToken: profileToken,
PresetTourToken: presetTourToken,
Operation: operation,
},
});
}
/**
* Operation to delete the specified preset tour.
* @param options
*/
async removePresetTour({ profileToken = this.onvif.activeSource.profileToken, presetTourToken, }) {
await this.request({
RemovePresetTour: {
ProfileToken: profileToken,
PresetTourToken: presetTourToken,
},
});
}
/**
* The SetPreset command saves the current device position parameters so that the device can move to the saved preset
* position through the GotoPreset operation. In order to create a new preset, the SetPresetRequest contains no
* PresetToken. If creation is successful, the Response contains the PresetToken which uniquely identifies the Preset.
* An existing Preset can be overwritten by specifying the PresetToken of the corresponding Preset. In both cases
* (overwriting or creation) an optional PresetName can be specified. The operation fails if the PTZ device is moving
* during the SetPreset operation. The device MAY internally save additional states such as imaging properties in the
* PTZ Preset which then should be recalled in the GotoPreset operation.
* @param options
* @param options.profileToken One of the device profile tokens, if omitted, uses profile token from the active source
* @param options.presetToken Preset token if we want to replace existing
* @returns Preset token
*/
async setPreset({ profileToken = this.onvif.activeSource.profileToken, presetName, presetToken, }) {
const response = await this.request({
SetPreset: {
ProfileToken: profileToken,
PresetName: presetName,
...(presetToken && { PresetToken: presetToken }),
},
});
return response.setPresetResponse.presetToken;
}
/**
* Operation to remove a PTZ preset for the Node in the selected profile.
* The operation is supported if the PresetPosition capability exists for the Node in the selected profile.
* @param options
*/
async removePreset({ profileToken = this.onvif.activeSource.profileToken, presetToken, }) {
await this.request({
RemovePreset: {
ProfileToken: profileToken,
PresetToken: presetToken,
},
});
}
static formatPTZSimpleVector({ pan, tilt, x, y, zoom } = {
x: 0,
y: 0,
zoom: 0,
}) {
return {
panTilt: {
x: pan || x,
y: tilt || y,
},
zoom: {
x: zoom,
},
};
}
static presetTourPresetDetailToXML(detail) {
const result = {};
if (detail.presetToken !== undefined) {
result.PresetToken = detail.presetToken;
}
if (detail.home !== undefined) {
result.Home = detail.home;
}
if (detail.PTZPosition !== undefined) {
result.PTZPosition = detail.PTZPosition;
}
if (detail.extension) {
result.Extension = detail.extension;
}
return result;
}
static presetTourSpotToXML(spot) {
return {
PresetDetail: PTZ.presetTourPresetDetailToXML(spot.presetDetail),
...(spot.speed && { Speed: PTZ.PTZVectorToXML(spot.speed) }),
...(spot.stayTime !== undefined && { StayTime: spot.stayTime }),
...(spot.extension && { Extension: spot.extension }),
};
}
static presetTourToXML(presetTour) {
return {
...(presetTour.token && {
$: { token: presetTour.token },
}),
...(presetTour.name && { Name: presetTour.name }),
...(presetTour.status && {
Status: {
State: presetTour.status.state,
...(presetTour.status.currentTourSpot && {
CurrentTourSpot: PTZ.presetTourSpotToXML(presetTour.status.currentTourSpot),
}),
...(presetTour.status.extension && { Extension: presetTour.status.extension }),
},
}),
...(presetTour.autoStart && { AutoStart: presetTour.autoStart }),
...(presetTour.startingCondition && {
StartingCondition: {
...(presetTour.startingCondition.randomPresetOrder && {
RandomPresetOrder: presetTour.startingCondition.randomPresetOrder,
}),
...(presetTour.startingCondition.recurringTime && {
RecurringTime: presetTour.startingCondition.recurringTime,
}),
...(presetTour.startingCondition.recurringDuration && {
RecurringDuration: presetTour.startingCondition.recurringDuration,
}),
...(presetTour.startingCondition.direction && {
Direction: presetTour.startingCondition.direction,
}),
...(presetTour.startingCondition.extension && {
Extension: presetTour.startingCondition.extension,
}),
},
}),
...(presetTour.tourSpot && {
TourSpot: presetTour.tourSpot.map((spot) => PTZ.presetTourSpotToXML(spot)),
}),
...(presetTour.extension && { Extension: presetTour.extension }),
};
}
static PTZVectorToXML(input) {
if (!input) {
return undefined;
}
const vector = 'x' in input || 'pan' in input ? PTZ.formatPTZSimpleVector(input) : input;
return (0, toOnvifXMLSchemaObject_1.ptzVectorToBuild)(vector);
}
/**
* Operation to go to a saved preset position for the PTZNode in the selected profile. The operation is supported if
* there is support for at least on PTZ preset by the PTZNode.
* @param options
*/
async gotoPreset({ profileToken = this.onvif.activeSource.profileToken, presetToken, speed, }) {
await this.request({
GotoPreset: {
ProfileToken: profileToken,
PresetToken: presetToken,
speed: PTZ.PTZVectorToXML(speed),
},
});
}
/**
* Operation to move the PTZ device to it's "home" position. The operation is supported if the HomeSupported element
* in the PTZNode is true.
* @param options
*/
async gotoHomePosition({ profileToken = this.onvif.activeSource.profileToken, speed, }) {
await this.request({
GotoHomePosition: {
ProfileToken: profileToken,
speed: PTZ.PTZVectorToXML(speed),
},
});
}
/**
* Operation to save current position as the home position. The SetHomePosition command returns with a failure if
* the “home” position is fixed and cannot be overwritten. If the SetHomePosition is successful, it is possible
* to recall the Home Position with the GotoHomePosition command.
* @param options
*/
async setHomePosition({ profileToken = this.onvif.activeSource.profileToken }) {
await this.request({
SetHomePosition: { ProfileToken: profileToken },
});
}
/**
* Operation to request PTZ status for the Node in the selected profile.
* @param options
*/
async getStatus({ profileToken = this.onvif.activeSource.profileToken }) {
const response = await this.request({
GetStatus: { ProfileToken: profileToken },
});
return response.getStatusResponse.PTZStatus;
}
/**
* Operation to move pan,tilt or zoom to a absolute destination.
*
* The speed argument is optional. If an x/y speed value is given it is up to the device to either use the x value as
* absolute resoluting speed vector or to map x and y to the component speed. If the speed argument is omitted, the
* default speed set by the PTZConfiguration will be used.
* @param options
*/
async absoluteMove({ profileToken = this.onvif.activeSource.profileToken, position, speed }) {
if (!position) {
throw new Error("'position' is required");
}
await this.request({
AbsoluteMove: {
ProfileToken: profileToken,
Position: PTZ.PTZVectorToXML(position),
Speed: PTZ.PTZVectorToXML(speed),
},
});
}
/**
* Operation for Relative Pan/Tilt and Zoom Move. The operation is supported if the PTZNode supports at least one
* relative Pan/Tilt or Zoom space.
*
* The speed argument is optional. If an x/y speed value is given it is up to the device to either use the x value as
* absolute resoluting speed vector or to map x and y to the component speed. If the speed argument is omitted,
* the default speed set by the PTZConfiguration will be used.
* @param options
*/
async relativeMove({ profileToken = this.onvif.activeSource.profileToken, translation, speed, }) {
if (!translation) {
throw new Error("'translation' is required");
}
await this.request({
RelativeMove: {
ProfileToken: profileToken,
Translation: PTZ.PTZVectorToXML(translation),
Speed: PTZ.PTZVectorToXML(speed),
},
});
}
/**
* Operation for continuous Pan/Tilt and Zoom movements. The operation is supported if the PTZNode supports at least
* one continuous Pan/Tilt or Zoom space. If the space argument is omitted, the default space set by the
* PTZConfiguration will be used.
* @param options
*/
async continuousMove({ profileToken = this.onvif.activeSource.profileToken, velocity, timeout, }) {
if (!velocity) {
throw new Error("'velocity' is required");
}
await this.request({
ContinuousMove: {
ProfileToken: profileToken,
Velocity: PTZ.PTZVectorToXML(velocity),
Timeout: typeof timeout === 'number' ? `PT${timeout / 1000}S` : timeout,
},
});
}
/**
* Operation to move pan,tilt or zoom to point to a destination based on the geolocation of the target.
*
* The speed argument is optional. If an x/y speed value is given it is up to the device to either use the x value as
* absolute resoluting speed vector or to map x and y to the component speed. If the speed argument is omitted,
* the default speed set by the PTZConfiguration will be used. The area height and area dwidth parameters are optional,
* they can be used independently and may be used by the device to automatically determine the best zoom level to show
* the target.
* @param options
*/
async geoMove(options) {
await this.request({
GeoMove: {
ProfileToken: options.profileToken ?? this.onvif.activeSource.profileToken,
Target: {
Lon: options.target.lon,
Lat: options.target.lat,
Elevation: options.target.elevation,
},
Speed: PTZ.PTZVectorToXML(options.speed),
AreaHeight: options.areaHeight,
AreaWidth: options.areaWidth,
},
});
}
/**
* Operation to move the camera to a target position and delegate PTZ control to the tracking algorithm.
* @param options
*/
async moveAndStartTracking({ profileToken = this.onvif.activeSource.profileToken, presetToken, geoLocation, targetPosition, speed, objectID, } = {}) {
return this.request({
MoveAndStartTracking: {
ProfileToken: profileToken,
...(presetToken && { PresetToken: presetToken }),
...(geoLocation && {
GeoLocation: {
Lon: geoLocation.lon,
Lat: geoLocation.lat,
Elevation: geoLocation.elevation,
},
}),
...(targetPosition && { TargetPosition: PTZ.PTZVectorToXML(targetPosition) }),
...(speed && { Speed: PTZ.PTZVectorToXML(speed) }),
...(objectID && { ObjectID: objectID }),
},
});
}
/**
* Operation to stop ongoing pan, tilt and zoom movements of absolute relative and continuous type. If no stop
* argument for pan, tilt or zoom is set, the device will stop all ongoing pan, tilt and zoom movements.
* @param options
*/
async stop(options) {
const profileToken = options?.profileToken || this.onvif?.activeSource?.profileToken;
const panTilt = options?.panTilt ?? true;
const zoom = options?.zoom ?? true;
await this.request({
Stop: {
ProfileToken: profileToken,
PanTilt: panTilt,
Zoom: zoom,
},
});
}
}
exports.default = PTZ;
//# sourceMappingURL=ptz.js.map