// relies on https://gitlab.com/Shinobi-Systems/shinobi-onvif const { mergeDeep } = require('../common.js') const replaceDynamicInOptions = (Camera,options) => { const newOptions = {} Object.keys(options).forEach((key) => { const value = options[key] if(typeof value === 'string'){ newOptions[key] = value.replace(/__CURRENT_TOKEN/g,Camera.current_profile.token) }else if(value !== undefined && value !== null){ newOptions[key] = value } }) return newOptions } const runOnvifMethod = async (onvifOptions) => { return new Promise((resolve,reject) => { const onvifDevice = onvifOptions.device var response = {ok: false} var errorMessage = function(msg,error){ response.ok = false response.msg = msg response.error = error resolve(response) } var actionCallback = function(onvifActionResponse){ response.ok = true if(onvifActionResponse.data){ response.responseFromDevice = onvifActionResponse.data }else{ response.responseFromDevice = onvifActionResponse } if(onvifActionResponse.soap)response.soap = onvifActionResponse.soap resolve(response) } var isEmpty = function(obj) { for(var key in obj) { if(obj.hasOwnProperty(key)) return false; } return true; } var completeAction = function(command){ if(command && command.then){ command.then(actionCallback).catch(function(error){ errorMessage('Device Action responded with an error',error) }) }else if(command){ response.ok = true response.repsonseFromDevice = command resolve(response) }else{ response.error = 'Big Errors, Please report it to Shinobi Development' resolve(response) } } var action if(onvifOptions.service){ if(onvifDevice.services[onvifOptions.service] === undefined){ return errorMessage('This is not an available service. Please use one of the following : '+Object.keys(onvifDevice.services).join(', ')) } if(onvifDevice.services[onvifOptions.service] === null){ return errorMessage('This service is not activated. Maybe you are not connected through ONVIF. You can test by attempting to use the "Control" feature with ONVIF in Shinobi.') } action = onvifDevice.services[onvifOptions.service][onvifOptions.action] }else{ action = onvifDevice[onvifOptions.action] } if(!action || typeof action !== 'function'){ errorMessage(onvifOptions.action+' is not an available ONVIF function. See https://github.com/futomi/node-onvif for functions.') }else{ var argNames = s.getFunctionParamNames(action) var options var command if(argNames[0] === 'options' || argNames[0] === 'params'){ options = replaceDynamicInOptions(onvifDevice,onvifOptions.options || {}) response.options = options } if(onvifOptions.service){ command = onvifDevice.services[onvifOptions.service][onvifOptions.action](options) }else{ command = onvifDevice[onvifOptions.action](options) } completeAction(command) } }) } const getDeviceInformation = async (onvifDevice,options) => { // const options = { // protocols: true, // networkInterface: true, // gateway: true, // dns: true, // ntp: true, // } const response = { ok: true, } try{ if(options.all || options.protocols){ response.protocols = (await onvifDevice.services.device.getNetworkProtocols()).data.GetNetworkProtocolsResponse.NetworkProtocols } if(options.all || options.networkInterface){ response.networkInterface = (await onvifDevice.services.device.getNetworkInterfaces()).data.GetNetworkInterfacesResponse.NetworkInterfaces } if(options.all || options.gatewayAddress){ response.gateway = (await onvifDevice.services.device.getNetworkDefaultGateway()).data.GetNetworkDefaultGatewayResponse.NetworkGateway.IPv4Address } if(options.all || options.dns){ response.dns = (await onvifDevice.services.device.getDNS()).data.GetDNSResponse.DNSInformation } if(options.all || options.ntp){ response.ntp = (await onvifDevice.services.device.getNTP()).data.GetNTPResponse.NTPInformation } if(options.all || options.date){ response.date = (await onvifDevice.services.device.getSystemDateAndTime()).data.GetSystemDateAndTimeResponse.SystemDateAndTime } if(options.all || options.users){ response.users = (await onvifDevice.services.device.getUsers()).data.GetUsersResponse.User } if(options.all || options.hostname){ response.hostname = (await onvifDevice.services.device.getHostname()).data.GetHostnameResponse.HostnameInformation.Name } if(options.all || options.discoveryMode){ response.discoveryMode = (await onvifDevice.services.device.getDiscoveryMode()).data.GetDiscoveryModeResponse.DiscoveryMode } if(options.all || options.videoEncoders){ response.videoEncoders = (await onvifDevice.services.media.getVideoEncoderConfigurations()).data.GetVideoEncoderConfigurationsResponse.Configurations } if(options.all || options.videoEncoderOptions){ response.videoEncoderOptions = (await runOnvifMethod({ device: onvifDevice, action: 'getVideoEncoderConfigurationOptions', service: 'media', options: {} })).responseFromDevice.GetVideoEncoderConfigurationOptionsResponse.Options } if(options.all || options.imagingSettings){ const imagingSettings = (await runOnvifMethod({ device: onvifDevice, action: 'getImagingSettings', service: 'imaging', options: {"ConfigurationToken":"__CURRENT_TOKEN"} })); response.imagingSettings = imagingSettings.responseFromDevice ? imagingSettings.responseFromDevice.GetImagingSettingsResponse.ImagingSettings : imagingSettings } }catch(err){ response.ok = false response.error = err.stack.toString().toString() s.debugLog(err) s.debugLog(onvifDevice) } return response } const setProtocols = async (onvifDevice,saveSet) => { // const saveSet = { // "HTTP": "80", // "RTSP": "554", // } const response = { ok: false } try{ const saveKeys = Object.keys(saveSet) const protocols = (await getDeviceInformation(onvifDevice,{protocols: true})).protocols protocols.forEach((item) => { saveKeys.forEach((key) => { if(item.Name === key.toUpperCase()){ const port = saveSet[key] && !isNaN(saveSet[key]) ? parseInt(saveSet[key]) : item.Name === 'RTSP' ? 554 : 80 console.log('PORT',port) item.Port = port item.Enabled = true } }) }) console.log('protocols',protocols) const onvifResponse = await onvifDevice.services.device.setNetworkProtocols({ NetworkProtocols: protocols }) response.ok = true response.onvifResponse = onvifResponse }catch(err){ response.error = err.stack.toString() } return response } const setNetworkInterface = async (onvifDevice,options) => { // const options = { // interface: 'eth0', // ipv4: '10.1.103.158', // dhcp: false, // } const response = { ok: false } try{ const { networkInterface } = await getDeviceInformation(onvifDevice,{networkInterface: true}) const onvifParams = { SetNetworkInterfaces: { InterfaceToken: options.interface || networkInterface.$.token, NetworkInterface: { Enabled: true, IPv4: { Enabled: true, Manual: { Address: options.ipv4 || networkInterface.IPv4.Config.Manual.Address, PrefixLength: '24', }, 'DHCP': options.dhcp === undefined || options.dhcp === null ? networkInterface.IPv4.Config.DHCP === 'true' ? true : false : options.dhcp, } } } } const onvifResponse = await onvifDevice.services.device.setNetworkInterfaces(onvifParams) response.ok = true response.onvifResponse = onvifResponse }catch(err){ response.error = err.stack.toString() } return response } const setGateway = async (onvifDevice,options) => { // const options = { // ipv4: '1.1.1.1,8.8.8.8', // } const response = { ok: false } try{ const gatewayAddress = (await getDeviceInformation(onvifDevice,{gateway: true})).gateway const onvifResponse = await onvifDevice.services.device.setNetworkDefaultGateway({ 'NetworkGateway': [ {'IPv4Address': options.ipv4 || gatewayAddress} ] }) response.ok = true response.onvifResponse = onvifResponse }catch(err){ response.error = err.stack.toString() } return response } const setDNS = async (onvifDevice,options) => { options = Object.assign({ dns: '1.1.1.1,8.8.8.8', searchDomain: 'localhost', dhcp: false, },options) const response = { ok: false } try{ const dnsArray = [] const searchDomain = (options.searchDomain || '').split(',') const dnsList = (options.dns || '1.1.1.1').split(',') dnsList.forEach((item) => { dnsArray.push({ Type: 'IPv4', IPv4Address: item, }) }) const dnsInfo = (await getDeviceInformation(onvifDevice,{dns: true})).dns const onvifResponse = await onvifDevice.services.device.setDNS({ 'FromDHCP' : !options.dhcp ? false : true, 'SearchDomain': searchDomain, 'DNSManual' : dnsArray }) response.ok = true response.onvifResponse = onvifResponse }catch(err){ response.error = err.stack.toString() } return response } const setNTP = async (onvifDevice,options) => { // const options = { // ipv4: '1.1.1.1,8.8.8.8', // dhcp: false, // } const response = { ok: false } try{ const ntpInfo = (await getDeviceInformation(onvifDevice,{ntp: true})).ntp const ipv4 = options.ipv4 || ntpInfo.NTPManual.IPv4Address const onvifResponse = await onvifDevice.services.device.setNTP({ FromDHCP: !options.dhcp ? false : true, NTPManual: {'Type': "IPv4", 'IPv4Address': ipv4} }) response.ok = true response.onvifResponse = onvifResponse }catch(err){ response.error = err.stack.toString() } return response } const setHostname = async (onvifDevice,options) => { // const options = { // name: 'hostname', // } const response = { ok: false } try{ const hostname = options.name || (await getDeviceInformation(onvifDevice,{hostname: true})).hostname const onvifResponse = await onvifDevice.services.device.setHostname({ Name: hostname }) response.ok = true response.onvifResponse = onvifResponse }catch(err){ response.error = err.stack.toString() } return response } const rebootCamera = async (onvifDevice,options) => { const response = { ok: false } try{ const onvifResponse = await onvifDevice.services.device.reboot() response.ok = true response.onvifResponse = onvifResponse }catch(err){ response.error = err.stack.toString() } return response } const setDateAndTime = async (onvifDevice,options) => { // const options = { // dateTimeType: 'ntp', // daylightSavings: false, // timezone: 'XXXX', // utcDateTime: 'XXXX', // } const response = { ok: false } try{ // const dateInfo = await onvifDevice.services.device.getSystemDateAndTime().GetSystemDateAndTimeResponse.SystemDateAndTime const onvifResponse = await onvifDevice.services.device.setSystemDateAndTime ({ DateTimeType: options.dateTimeType, DaylightSavings: !options.daylightSavings ? false : true, // TimeZone: options.timezone, UTCDateTime: new Date(options.utcDateTime), }) response.ok = true response.onvifResponse = onvifResponse }catch(err){ response.error = err.stack.toString() } return response } const createUser = async (onvifDevice,options) => { // const options = { // name: 'user1', // password: '123', // level: 'Administrator' || 'Operator' || 'User', // } const response = { ok: false } try{ const onvifResponse = await onvifDevice.services.device.createUsers({ 'User' : [ {'Username': options.name, 'Password' : options.password, 'UserLevel': options.level} ] }) response.ok = true response.onvifResponse = onvifResponse }catch(err){ response.error = err.stack.toString() } return response } const deleteUser = async (onvifDevice,options) => { // const options = { // name: 'user1', // } const response = { ok: false } try{ const onvifResponse = await onvifDevice.services.device.deleteUsers({ 'User' : [ {'Username': options.name} ] }) response.ok = true response.onvifResponse = onvifResponse }catch(err){ response.error = err.stack.toString() } return response } const validateEncoderOptions = (chosenOptions, videoEncoderOptions) => { const resolutions = [] const minQuality = parseInt(videoEncoderOptions.QualityRange.Min) || 1 const maxQuality = parseInt(videoEncoderOptions.QualityRange.Max) || 6 const quality = !isNaN(chosenOptions.Quality) ? parseInt(chosenOptions.Quality) : 1 const minGovLength = parseInt(videoEncoderOptions.H264.GovLengthRange.Min) || 0 const maxGovLength = parseInt(videoEncoderOptions.H264.GovLengthRange.Max) || 100 const govLength = !isNaN(chosenOptions.H264.GovLength) ? parseInt(chosenOptions.H264.GovLength) : 10 const minFrameRateLimit = parseInt(videoEncoderOptions.H264.FrameRateRange.Min) || 1 const maxFrameRateLimit = parseInt(videoEncoderOptions.H264.FrameRateRange.Max) || 30 const frameRateLimit = !isNaN(chosenOptions.RateControl.FrameRateLimit) ? parseInt(chosenOptions.RateControl.FrameRateLimit) : 15 const minEncodingInterval = parseInt(videoEncoderOptions.H264.EncodingIntervalRange.Min) || 1 const maxEncodingInterval = parseInt(videoEncoderOptions.H264.EncodingIntervalRange.Max) || 30 const encodingInterval = !isNaN(chosenOptions.RateControl.EncodingInterval) ? parseInt(chosenOptions.RateControl.EncodingInterval) : 1 videoEncoderOptions.H264.ResolutionsAvailable.forEach((resolution) => { resolutions.push(`${resolution.Width}x${resolution.Height}`) }) if(resolutions.indexOf(`${chosenOptions.Resolution.Width}x${chosenOptions.Resolution.Height}`) === -1){ chosenOptions.Resolution.Width = resolutions[0].Width chosenOptions.Resolution.Height = resolutions[0].Height } if(quality < minQuality || quality > maxQuality){ chosenOptions.Quality = 1 } if(govLength < minGovLength || govLength > maxGovLength){ chosenOptions.H264.GovLength = 10 } if(frameRateLimit < minFrameRateLimit || frameRateLimit > maxFrameRateLimit){ chosenOptions.RateControl.FrameRateLimit = 15 } if(encodingInterval < minEncodingInterval || encodingInterval > maxEncodingInterval){ chosenOptions.RateControl.EncodingInterval = 1 } } const setVideoConfiguration = async (onvifDevice,options) => { const response = { ok: false } try{ const { videoEncoders, videoEncoderOptions } = await getDeviceInformation(onvifDevice,{ videoEncoders: true, videoEncoderOptions: true }) const videoEncoderIndex = {} videoEncoders.forEach((encoder) => {videoEncoderIndex[encoder.$.token] = encoder}) const chosenToken = `${options.videoToken || videoEncoders[0].$.token}` const videoEncoder = videoEncoderIndex[chosenToken] || {} const onvifParams = mergeDeep(videoEncoder,options,{ Multicast: { AutoStart: !options.Multicast || !options.Multicast.AutoStart ? 'false' : 'true' }, }) const validatedEncoderOptions = validateEncoderOptions(onvifParams, videoEncoderOptions) const onvifResponse = await onvifDevice.services.media.setVideoEncoderConfiguration({ Configuration: onvifParams, ConfigurationToken: chosenToken }) response.ok = true response.onvifResponse = onvifResponse }catch(err){ response.error = err.stack.toString() } return response } const setImagingSettings = async (onvifDevice,options) => { const response = { ok: false } try{ const { imagingSettings } = await getDeviceInformation(onvifDevice,{imagingSettings: true}) const onvifParams = mergeDeep(imagingSettings,options) const onvifResponse = await onvifDevice.services.imaging.setImagingSettings({ ConfigurationToken: options.videoToken || onvifDevice.current_profile.token, ImagingSettings: onvifParams }) response.ok = true response.onvifResponse = onvifResponse }catch(err){ response.error = err.stack.toString() } return response } const setDiscoveryMode = async (onvifDevice,options) => { const response = { ok: false } try{ const onvifResponse = await onvifDevice.services.device.setDiscoveryMode(options.mode === 'Discoverable' || options.mode === 'NonDiscoverable' ? options.mode : 'Discoverable') response.ok = true response.onvifResponse = onvifResponse }catch(err){ response.error = err.stack.toString() } return response } const getUIFieldValues = async (onvifDevice) => { return await getDeviceInformation(onvifDevice,{ all: true, }) } module.exports = { getDeviceInformation: getDeviceInformation, setHostname: setHostname, setProtocols: setProtocols, setGateway: setGateway, setDNS: setDNS, setNTP: setNTP, rebootCamera: rebootCamera, setDateAndTime: setDateAndTime, createUser: createUser, deleteUser: deleteUser, setVideoConfiguration: setVideoConfiguration, setImagingSettings: setImagingSettings, setDiscoveryMode: setDiscoveryMode, setNetworkInterface: setNetworkInterface, getUIFieldValues: getUIFieldValues, }