text: /* ------------------------------------------------------------------
  * node-onvif - service-device.js
  *
  * Copyright (c) 2016, Futomi Hatano, All rights reserved.
  * Released under the MIT license
  * Date: 2016-10-02
  * ---------------------------------------------------------------- */
  'use strict';
  const mUrl = require('url');
  const mOnvifSoap = require('./soap.js');
  
  /* ------------------------------------------------------------------
  * Constructor: OnvifServiceDevice(params)
  * - params:
  *    - xaddr   : URL of the entry point for the device management service
  *                (Required)
  *    - user  : User name (Optional)
  *    - pass  : Password (Optional)
  * ---------------------------------------------------------------- */
  function OnvifServiceDevice(params) {
  	this.xaddr = '';
  	this.user = '';
  	this.pass = '';
  
  	let err_msg = '';
  
  	if (err_msg = mOnvifSoap.isInvalidValue(params, 'object')) {
  		throw new Error('The value of "params" was invalid: ' + err_msg);
  	}
  
  	if ('xaddr' in params) {
  		if (err_msg = mOnvifSoap.isInvalidValue(params['xaddr'], 'string')) {
  			throw new Error('The "xaddr" property was invalid: ' + err_msg);
  		} else {
  			this.xaddr = params['xaddr'];
  		}
  	} else {
  		throw new Error('The "xaddr" property is required.');
  	}
  
  	if ('user' in params) {
  		if (err_msg = mOnvifSoap.isInvalidValue(params['user'], 'string', true)) {
  			throw new Error('The "user" property was invalid: ' + err_msg);
  		} else {
  			this.user = params['user'] || '';
  		}
  	}
  
  	if ('pass' in params) {
  		if (err_msg = mOnvifSoap.isInvalidValue(params['pass'], 'string', true)) {
  			throw new Error('The "pass" property was invalid: ' + err_msg);
  		} else {
  			this.pass = params['pass'] || '';
  		}
  	}
  
  	this.oxaddr = mUrl.parse(this.xaddr);
  	if (this.user) {
  		this.oxaddr.auth = this.user + ':' + this.pass;
  	}
  
  	this.time_diff = 0;
  	this.name_space_attr_list = [
  		'xmlns:tds="http://www.onvif.org/ver10/device/wsdl"',
  		'xmlns:tt="http://www.onvif.org/ver10/schema"'
  	];
  }
  
  OnvifServiceDevice.prototype._createRequestSoap = function (body) {
  	let soap = mOnvifSoap.createRequestSoap({
  		'body': body,
  		'xmlns': this.name_space_attr_list,
  		'diff': this.time_diff,
  		'user': this.user,
  		'pass': this.pass
  	});
  	return soap;
  };
  
  /* ------------------------------------------------------------------
  * Method: getTimeDiff()
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.getTimeDiff = function () {
  	return this.time_diff;
  };
  
  /* ------------------------------------------------------------------
  * Method: setAuth(user, pass)
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.setAuth = function (user, pass) {
  	this.user = user || '';
  	this.pass = pass || '';
  	if (this.user) {
  		this.oxaddr.auth = this.user + ':' + this.pass;
  	} else {
  		this.oxaddr.auth = '';
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: getCapabilities([callback])
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.getCapabilities = function (callback) {
  	let promise = new Promise((resolve, reject) => {
  		let soap_body = '';
  		soap_body += '<tds:GetCapabilities>';
  		soap_body += '  <tds:Category>All</tds:Category>';
  		soap_body += '</tds:GetCapabilities>';
  		let soap = this._createRequestSoap(soap_body);
  		mOnvifSoap.requestCommand(this.oxaddr, 'GetCapabilities', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: getWsdlUrl([callback])
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.getWsdlUrl = function (callback) {
  	let promise = new Promise((resolve, reject) => {
  		let soap_body = '<tds:GetWsdlUrl/>';
  		let soap = this._createRequestSoap(soap_body);
  		mOnvifSoap.requestCommand(this.oxaddr, 'GetWsdlUrl', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: getDiscoveryMode(callback)
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.getDiscoveryMode = function (callback) {
  	let promise = new Promise((resolve, reject) => {
  		let soap_body = '<tds:GetDiscoveryMode/>';
  		let soap = this._createRequestSoap(soap_body);
  		mOnvifSoap.requestCommand(this.oxaddr, 'GetDiscoveryMode', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: getScopes([callback])
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.getScopes = function (callback) {
  	let promise = new Promise((resolve, reject) => {
  		let soap_body = '<tds:GetScopes/>';
  		let soap = this._createRequestSoap(soap_body);
  		mOnvifSoap.requestCommand(this.oxaddr, 'GetScopes', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: setScopes(params[, callback])
  * - params:
  *   - Scopes  | Array  | required | a list of URI
  *
  * {
  *   'Scopes': [
  *     'onvif://www.onvif.org/location/town/Nerima',
  *     'onvif://www.onvif.org/location/city/Tokyo'
  *   ]
  * }
  *
  * If you want to delete all Configurable scopes, specify an empty
  * Array object as the `Scope` property.
  *
  * {'Scopes': []}
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.setScopes = function (params, callback) {
  	let promise = new Promise((resolve, reject) => {
  		let err_msg = '';
  		if (err_msg = mOnvifSoap.isInvalidValue(params, 'object')) {
  			reject(new Error('The value of "params" was invalid: ' + err_msg));
  			return;
  		}
  
  		let scope_list = params['Scopes'];
  		if (err_msg = mOnvifSoap.isInvalidValue(scope_list, 'array', true)) {
  			reject(new Error('The "Scopes" property was invalid: ' + err_msg));
  			return;
  		}
  
  		let soap_body = '';
  		soap_body += '<tds:SetScopes>';
  		scope_list.forEach((s) => {
  			soap_body += '<tds:Scopes>' + s + '</tds:Scopes>';
  		});
  		soap_body += '</tds:SetScopes>';
  		let soap = this._createRequestSoap(soap_body);
  
  		mOnvifSoap.requestCommand(this.oxaddr, 'SetScopes', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: addScopes(params[, callback])
  * - params:
  *   - Scopes  | Array  | required | a list of URI
  *
  * {
  *   'Scopes': [
  *     'onvif://www.onvif.org/location/town/Nerima',
  *     'onvif://www.onvif.org/location/city/Tokyo'
  *   ]
  * }
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.addScopes = function (params, callback) {
  	let promise = new Promise((resolve, reject) => {
  		let err_msg = '';
  		if (err_msg = mOnvifSoap.isInvalidValue(params, 'object')) {
  			reject(new Error('The value of "params" was invalid: ' + err_msg));
  			return;
  		}
  
  		let scope_list = params['Scopes'];
  		if (err_msg = mOnvifSoap.isInvalidValue(scope_list, 'array', true)) {
  			reject(new Error('The "Scopes" property was invalid: ' + err_msg));
  			return;
  		}
  
  		let soap_body = '';
  		soap_body += '<tds:AddScopes>';
  		scope_list.forEach((s) => {
  			soap_body += '<tds:ScopeItem>' + s + '</tds:ScopeItem>';
  		});
  		soap_body += '</tds:AddScopes>';
  		let soap = this._createRequestSoap(soap_body);
  
  		mOnvifSoap.requestCommand(this.oxaddr, 'AddScopes', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: removeScopes(params[, callback])
  * - params:
  *   - Scopes  | Array  | required | a list of URI
  *
  * {
  *   'Scopes': [
  *     'onvif://www.onvif.org/location/town/Nerima',
  *     'onvif://www.onvif.org/location/city/Tokyo'
  *   ]
  * }
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.removeScopes = function (params, callback) {
  	let promise = new Promise((resolve, reject) => {
  		let err_msg = '';
  		if (err_msg = mOnvifSoap.isInvalidValue(params, 'object')) {
  			reject(new Error('The value of "params" was invalid: ' + err_msg));
  			return;
  		}
  
  		let scope_list = params['Scopes'];
  		if (err_msg = mOnvifSoap.isInvalidValue(scope_list, 'array')) {
  			reject(new Error('The "Scopes" property was invalid: ' + err_msg));
  			return;
  		}
  
  		let soap_body = '';
  		soap_body += '<tds:RemoveScopes>';
  		scope_list.forEach((s) => {
  			soap_body += '<tds:ScopeItem>' + s + '</tds:ScopeItem>';
  		});
  		soap_body += '</tds:RemoveScopes>';
  		let soap = this._createRequestSoap(soap_body);
  
  		mOnvifSoap.requestCommand(this.oxaddr, 'RemoveScopes', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: getHostname([callback])
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.getHostname = function (callback) {
  	let promise = new Promise((resolve, reject) => {
  		let soap_body = '<tds:GetHostname/>';
  		let soap = this._createRequestSoap(soap_body);
  		mOnvifSoap.requestCommand(this.oxaddr, 'GetHostname', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: setHostname(params[, callback])
  * - params:
  *   - Name  | string  | required | a host name
  *
  * {'Name': 'test'}
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.setHostname = function (params, callback) {
  	let promise = new Promise((resolve, reject) => {
  		let err_msg = '';
  		if (err_msg = mOnvifSoap.isInvalidValue(params, 'object')) {
  			reject(new Error('The value of "params" was invalid: ' + err_msg));
  			return;
  		}
  
  		let hostname = params['Name'];
  		if (err_msg = mOnvifSoap.isInvalidValue(hostname, 'string')) {
  			reject(new Error('The "Name" property was invalid: ' + err_msg));
  			return;
  		}
  
  		let soap_body = '';
  		soap_body += '<tds:SetHostname>';
  		soap_body += '<tds:Name>' + hostname + '</tds:Name>';
  		soap_body += '</tds:SetHostname>';
  		let soap = this._createRequestSoap(soap_body);
  
  		mOnvifSoap.requestCommand(this.oxaddr, 'SetHostname', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: getDNS([callback])
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.getDNS = function (callback) {
  	let promise = new Promise((resolve, reject) => {
  		let soap_body = '<tds:GetDNS/>';
  		let soap = this._createRequestSoap(soap_body);
  		mOnvifSoap.requestCommand(this.oxaddr, 'GetDNS', soap).then((result) => {
  			try {
  				let di = result['data']['DNSInformation'];
  				if (!di['SearchDomain']) {
  					di['SearchDomain'] = [];
  				} else if (!Array.isArray(di['SearchDomain'])) {
  					di['SearchDomain'] = [di['SearchDomain']];
  				}
  				if (!di['DNSManual']) {
  					di['DNSManual'] = [];
  				} else if (!Array.isArray(di['DNSManual'])) {
  					di['DNSManual'] = [di['DNSManual']];
  				}
  				result['data'] = di;
  			} catch (e) { }
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: setDNS(params[, callback])
  * - params:
  *   - FromDHCP      | boolean | required | true or false
  *   - SearchDomain  | Array   | optional | a list of search domains
  *   - DNSManual     | Array   | optional | a list of DNS addresses
  *     - Type        | String  | required | "IPv4" or "IPv6"
  *     - IPv4Address | String  | optional | IPv4 address
  *     - IPv6Address | String  | optional | IPv6 address
  *
  * {
  *   'FromDHCP'    : false,
  *   'SearchDomain': ['futomi.gr.jp', 'hatano.gr.jp'],
  *   'DNSManual'   : [
  *     {'Type': 'IPv4', 'IPv4Address': '192.168.10.1'},
  *     {'Type': 'IPv4', 'IPv4Address': '192.168.10.2'}
  *   ]
  * }
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.setDNS = function (params, callback) {
  	let promise = new Promise((resolve, reject) => {
  		let err_msg = '';
  		if (err_msg = mOnvifSoap.isInvalidValue(params, 'object')) {
  			reject(new Error('The value of "params" was invalid: ' + err_msg));
  			return;
  		}
  
  		if (err_msg = mOnvifSoap.isInvalidValue(params['FromDHCP'], 'boolean')) {
  			reject(new Error('The "FromDHCP" property was invalid: ' + err_msg));
  			return;
  		}
  
  		if ('SearchDomain' in params) {
  			if (err_msg = mOnvifSoap.isInvalidValue(params['SearchDomain'], 'array', true)) {
  				reject(new Error('The "SearchDomain" property was invalid: ' + err_msg));
  				return;
  			}
  			for (let i = 0; i < params['SearchDomain'].length; i++) {
  				if (err_msg = mOnvifSoap.isInvalidValue(params['SearchDomain'][i], 'string')) {
  					reject(new Error('The "SearchDomain" property was invalid: ' + err_msg));
  					return;
  				}
  			}
  		}
  
  		if ('DNSManual' in params) {
  			if (err_msg = mOnvifSoap.isInvalidValue(params['DNSManual'], 'array', true)) {
  				reject(new Error('The "DNSManual" property was invalid: ' + err_msg));
  				return;
  			}
  
  			for (let i = 0; i < params['DNSManual'].length; i++) {
  				let o = params['DNSManual'][i];
  				if (err_msg = mOnvifSoap.isInvalidValue(o, 'object')) {
  					reject(new Error('The "DNSManual" property was invalid: ' + err_msg));
  					return;
  				}
  
  				let type = o['Type'];
  				if (err_msg = mOnvifSoap.isInvalidValue(type, 'string')) {
  					reject(new Error('The "Type" property was invalid: ' + err_msg));
  					return;
  				} else if (!type.match(/^(IPv4|IPv6)$/)) {
  					reject(new Error('The "Type" property was invalid: The value must be either "IPv4" or "IPv6".'));
  					return;
  				}
  
  				if (type === 'IPv4') {
  					if (err_msg = mOnvifSoap.isInvalidValue(o['IPv4Address'], 'string')) {
  						reject(new Error('The "IPv4Address" property was invalid: ' + err_msg));
  						return;
  					}
  				} else if (type === 'IPv6') {
  					if (err_msg = mOnvifSoap.isInvalidValue(o['IPv6Address'], 'string')) {
  						reject(new Error('The "IPv6Address" property was invalid: ' + err_msg));
  						return;
  					}
  				}
  			}
  		}
  
  		let soap_body = '';
  		soap_body += '<tds:SetDNS>';
  		if ('FromDHCP' in params) {
  			soap_body += '<tds:FromDHCP>' + params['FromDHCP'] + '</tds:FromDHCP>';
  		}
  		if ('SearchDomain' in params) {
  			params['SearchDomain'].forEach((s) => {
  				soap_body += '<tds:SearchDomain>' + s + '</tds:SearchDomain>';
  			});
  		}
  		if ('DNSManual' in params) {
  			if (params['DNSManual'].length === 0) {
  				soap_body += '<tds:DNSManual></tds:DNSManual>';
  			} else {
  				params['DNSManual'].forEach((o) => {
  					soap_body += '<tds:DNSManual>';
  					soap_body += '<tt:Type>' + o['Type'] + '</tt:Type>';
  					if (o['Type'] === 'IPv4') {
  						soap_body += '<tt:IPv4Address>' + o['IPv4Address'] + '</tt:IPv4Address>';
  					} else {
  						soap_body += '<tt:IPv6Address>' + o['IPv6Address'] + '</tt:IPv6Address>';
  					}
  					soap_body += '</tds:DNSManual>';
  				});
  			}
  		}
  		soap_body += '</tds:SetDNS>';
  
  		let soap = this._createRequestSoap(soap_body);
  
  		mOnvifSoap.requestCommand(this.oxaddr, 'SetDNS', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: getNetworkInterfaces([callback])
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.getNetworkInterfaces = function (callback) {
  	let promise = new Promise((resolve, reject) => {
  		let soap_body = '<tds:GetNetworkInterfaces/>';
  		let soap = this._createRequestSoap(soap_body);
  		mOnvifSoap.requestCommand(this.oxaddr, 'GetNetworkInterfaces', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: getNetworkProtocols([callback])
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.getNetworkProtocols = function (callback) {
  	let promise = new Promise((resolve, reject) => {
  		let soap_body = '<tds:GetNetworkProtocols/>';
  		let soap = this._createRequestSoap(soap_body);
  		mOnvifSoap.requestCommand(this.oxaddr, 'GetNetworkProtocols', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: setNetworkProtocols(params[, callback])
  * - params:
  *   - NetworkProtocols | Array   | required | a list of protocols
  *     - Name           | String  | required |
  *     - Enabled        | Boolean | optional |
  *     - Port           | Integer | optional |
  *
  * {
  *   'NetworkProtocols': [
  *     {'Name': 'HTTP', 'Enabled': true, 'Port': 80},
  *     {'Name': 'RTSP', 'Enabled': false, 'Port': 554},
  *   ]
  * }
  *
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.setNetworkProtocols = function (params, callback) {
  	let promise = new Promise((resolve, reject) => {
  		let err_msg = '';
  		if (err_msg = mOnvifSoap.isInvalidValue(params, 'object')) {
  			callback(new Error('The value of "params" was invalid: ' + err_msg));
  			return;
  		}
  
  		if (err_msg = mOnvifSoap.isInvalidValue(params['NetworkProtocols'], 'array', true)) {
  			callback(new Error('The "NetworkProtocols" property was invalid: ' + err_msg));
  			return;
  		}
  
  		for (let i = 0; i < params['NetworkProtocols'].length; i++) {
  			let o = params['NetworkProtocols'][i];
  			if (err_msg = mOnvifSoap.isInvalidValue(o, 'object')) {
  				callback(new Error('The "NetworkProtocols" property was invalid: ' + err_msg));
  				return;
  			}
  
  			if (err_msg = mOnvifSoap.isInvalidValue(o['Name'], 'string')) {
  				callback(new Error('The "Name" property was invalid: ' + err_msg));
  				return;
  			} else if (!o['Name'].match(/^(HTTP|HTTPS|RTSP)$/)) {
  				callback(new Error('The "Name" property was invalid: It must be "HTTP", "HTTPS", or "RTSP".'));
  				return;
  			}
  
  			let flag = false;
  
  			if ('Enabled' in o) {
  				if (err_msg = mOnvifSoap.isInvalidValue(o['Enabled'], 'boolean')) {
  					callback(new Error('The "Enabled" property was invalid: ' + err_msg));
  					return;
  				}
  				flag = true;
  			}
  
  			if ('Port' in o) {
  				if (err_msg = mOnvifSoap.isInvalidValue(o['Port'], 'integer')) {
  					callback(new Error('The "Port" property was invalid: ' + err_msg));
  					return;
  				}
  				flag = true;
  			}
  
  			if (flag === false) {
  				callbackError('Either "Enabled" or "Port" property is required.');
  				return;
  			}
  		}
  
  		let soap_body = '';
  		soap_body += '<tds:SetNetworkProtocols>';
  		params['NetworkProtocols'].forEach((o) => {
  			soap_body += '<tds:NetworkProtocols>';
  			for (let k in o) {
  				if (k.match(/^(Name|Enabled|Port)$/)) {
  					soap_body += '<tt:' + k + '>' + o[k] + '</tt:' + k + '>';
  				}
  			}
  			soap_body += '</tds:NetworkProtocols>';
  		});
  		soap_body += '</tds:SetNetworkProtocols>';
  		let soap = this._createRequestSoap(soap_body);
  		mOnvifSoap.requestCommand(this.oxaddr, 'SetNetworkProtocols', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: getNetworkDefaultGateway([callback])
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.getNetworkDefaultGateway = function (callback) {
  	let promise = new Promise((resolve, reject) => {
  		let soap_body = '<tds:GetNetworkDefaultGateway/>';
  		let soap = this._createRequestSoap(soap_body);
  		mOnvifSoap.requestCommand(this.oxaddr, 'GetNetworkDefaultGateway', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: setNetworkDefaultGateway(params[, callback])
  * - params:
  *   - NetworkGateway | Array | required | a list of IP addresses of gateways
  *
  * {
  *   'NetworkGateway': [
  *     {'IPv4Address': '192.168.10.1'},
  *     {'IPv4Address': '192.168.10.2'}
  *   ]
  * }
  *
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.setNetworkDefaultGateway = function (params, callback) {
  	let promise = new Promise((resolve, reject) => {
  		let err_msg = '';
  		if (err_msg = mOnvifSoap.isInvalidValue(params, 'object')) {
  			reject(new Error('The value of "params" was invalid: ' + err_msg));
  			return;
  		}
  
  		if (err_msg = mOnvifSoap.isInvalidValue(params['NetworkGateway'], 'array', true)) {
  			reject(new Error('The "NetworkGateway" property was invalid: ' + err_msg));
  			return;
  		}
  
  		for (let i = 0; i < params['NetworkGateway'].length; i++) {
  			let o = params['NetworkGateway'][i];
  			if ('IPv4Address' in o) {
  				if (err_msg = mOnvifSoap.isInvalidValue(o['IPv4Address'], 'string')) {
  					reject(new Error('The "IPv4Address" property was invalid: ' + err_msg));
  					return;
  				}
  			}
  			if ('IPv6Address' in o) {
  				if (err_msg = mOnvifSoap.isInvalidValue(o['IPv6Address'], 'string')) {
  					reject(new Error('The "IPv6Address" property was invalid: ' + err_msg));
  					return;
  				}
  			}
  			if (!('IPv4Address' in o) && !('IPv6Address' in o)) {
  				reject(new Error('Either "IPv4Address" or "IPv6Address" property must be specified.'));
  				return;
  			}
  		}
  
  		let soap_body = '';
  		soap_body += '<tds:SetNetworkDefaultGateway>';
  		params['NetworkGateway'].forEach((o) => {
  			for (let k in o) {
  				if (k.match(/^(IPv4Address|IPv6Address)$/)) {
  					soap_body += '<tds:' + k + '>' + o[k] + '</tds:' + k + '>';
  				}
  			}
  		});
  		soap_body += '</tds:SetNetworkDefaultGateway>';
  		let soap = this._createRequestSoap(soap_body);
  
  		mOnvifSoap.requestCommand(this.oxaddr, 'SetNetworkDefaultGateway', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: getDeviceInformation([callback])
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.getDeviceInformation = function (callback) {
  	let promise = new Promise((resolve, reject) => {
  		let soap_body = '<tds:GetDeviceInformation/>';
  		let soap = this._createRequestSoap(soap_body);
  		mOnvifSoap.requestCommand(this.oxaddr, 'GetDeviceInformation', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: getSystemDateAndTime([callback])
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.getSystemDateAndTime = function (callback) {
  	let promise = new Promise((resolve, reject) => {
  		let soap_body = '<tds:GetSystemDateAndTime/>';
  		let soap = this._createRequestSoap(soap_body);
  		mOnvifSoap.requestCommand(this.oxaddr, 'GetSystemDateAndTime', soap).then((result) => {
  			let parsed = this._parseGetSystemDateAndTime(result['converted']);
  			if (parsed && parsed['date']) {
  				let device_time = parsed['date'].getTime();
  				let my_time = (new Date()).getTime();
  				this.time_diff = device_time - my_time;
  			}
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  OnvifServiceDevice.prototype._parseGetSystemDateAndTime = function (s) {
  	let s0 = s['Body'];
  	if (!s0) { return null; }
  	let s1 = s0['GetSystemDateAndTimeResponse'];
  	if (!s1) { return null; }
  	let s2 = s1['SystemDateAndTime'];
  	if (!s2) { return null; }
  
  	let type = s2['DateTimeType'] || '';
  	let dst = null;
  	if (s2['DaylightSavings']) {
  		dst = (s2['DaylightSavings'] === 'true') ? true : false;
  	}
  	let tz = (s2['TimeZone'] && s2['TimeZone']['TZ']) ? s2['TimeZone']['TZ'] : '';
  	let date = null;
  	if (s2['UTCDateTime']) {
  		let udt = s2['UTCDateTime'];
  		let t = udt['Time'];
  		let d = udt['Date'];
  		if (t && d && t['Hour'] && t['Minute'] && t['Second'] && d['Year'] && d['Month'] && d['Day']) {
  			date = new Date();
  			date.setUTCFullYear(parseInt(d['Year'], 10));
  			date.setUTCMonth(parseInt(d['Month'], 10) - 1);
  			date.setUTCDate(parseInt(d['Day'], 10));
  			date.setUTCHours(parseInt(t['Hour'], 10));
  			date.setUTCMinutes(parseInt(t['Minute'], 10));
  			date.setUTCSeconds(parseInt(t['Second'], 10));
  		}
  	}
  	let res = {
  		'type': type,
  		'dst': dst,
  		'tz': tz,
  		'date': date
  	};
  	return res;
  };
  
  /* ------------------------------------------------------------------
  * Method: setSystemDateAndTime(params[, callback])
  * - params:
  *   - DateTimeType    | string  | required | "NTP" or "Manual".
  *   - DaylightSavings | boolean | required | true or false.
  *   - TimeZone        | string  | optional | e.g., "EST5EDT", "GMT0", "JST-9".
  *   - UTCDateTime     | Date    | optional | A Date object of ECMAScript.
  *
  * Setting the "UTCDateTime" does not work well for now.
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.setSystemDateAndTime = function (params, callback) {
  	let promise = new Promise((resolve, reject) => {
  		let err_msg = '';
  		if (err_msg = mOnvifSoap.isInvalidValue(params, 'object')) {
  			reject(new Error('The value of "params" was invalid: ' + err_msg));
  			return;
  		}
  
  		if (err_msg = mOnvifSoap.isInvalidValue(params['DateTimeType'], 'string')) {
  			reject(new Error('The "DateTimeType" property was invalid: ' + err_msg));
  			return;
  		} else if (!params['DateTimeType'].match(/^(NTP|Manual)$/)) {
  			reject(new Error('The "DateTimeType" property must be either "NTP" or "Manual".'));
  			return;
  		}
  
  		if (err_msg = mOnvifSoap.isInvalidValue(params['DaylightSavings'], 'boolean')) {
  			reject(new Error('The "DaylightSavings" property was invalid: ' + err_msg));
  			return;
  		}
  
  		if ('TimeZone' in params) {
  			if (err_msg = mOnvifSoap.isInvalidValue(params['TimeZone'], 'string')) {
  				reject(new Error('The "TimeZone" property was invalid: ' + err_msg));
  				return;
  			} else if (!params['TimeZone'].match(/^[A-Z]{3}\-?\d{1,2}([A-Z]{3,4})?$/)) {
  				reject(new Error('The "TimeZone" property must be a string representing a time zone which is defined in POSIX 1003.1.'));
  				return;
  			}
  		}
  
  		if ('UTCDateTime' in params) {
  			let v = params['UTCDateTime'];
  			if (!v instanceof Date) {
  				reject(new Error('The "UTCDateTime" property must be a Date object of ECMAScript.'));
  				return;
  			}
  		}
  
  		let soap_body = '';
  		soap_body += '<tds:SetSystemDateAndTime>';
  		soap_body += '<tds:DateTimeType>' + params['DateTimeType'] + '</tds:DateTimeType>';
  		soap_body += '<tds:DaylightSavings>' + params['DaylightSavings'] + '</tds:DaylightSavings>';
  		if (params['TimeZone']) {
  			soap_body += '<tds:TimeZone>';
  			soap_body += '<tt:TZ>' + params['TimeZone'] + '</tt:TZ>';
  			soap_body += '</tds:TimeZone>';
  		}
  		if (params['UTCDateTime']) {
  			let dt = params['UTCDateTime'];
  			soap_body += '<tds:UTCDateTime>';
  			soap_body += '<tt:Time>';
  			soap_body += '<tt:Hour>' + dt.getUTCHours() + '</tt:Hour>';
  			soap_body += '<tt:Minute>' + dt.getUTCMinutes() + '</tt:Minute>';
  			soap_body += '<tt:Second>' + dt.getUTCSeconds() + '</tt:Second>';
  			soap_body += '</tt:Time>';
  			soap_body += '<tt:Date>';
  			soap_body += '<tt:Year>' + dt.getUTCFullYear() + '</tt:Year>';
  			soap_body += '<tt:Month>' + (dt.getUTCMonth() + 1) + '</tt:Month>';
  			soap_body += '<tt:Day>' + dt.getUTCDate() + '</tt:Day>';
  			soap_body += '</tt:Date>';
  			soap_body += '</tds:UTCDateTime>';
  		}
  		soap_body += '</tds:SetSystemDateAndTime>';
  		let soap = this._createRequestSoap(soap_body);
  
  		mOnvifSoap.requestCommand(this.oxaddr, 'SetSystemDateAndTime', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: reboot([callback])
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.reboot = function (callback) {
  	let promise = new Promise((resolve, reject) => {
  		let soap_body = '<tds:SystemReboot/>';
  		let soap = this._createRequestSoap(soap_body);
  		mOnvifSoap.requestCommand(this.oxaddr, 'SystemReboot', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: getUsers([callback])
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.getUsers = function (callback) {
  	let promise = new Promise((resolve, reject) => {
  		let soap_body = '<tds:GetUsers/>';
  		let soap = this._createRequestSoap(soap_body);
  		mOnvifSoap.requestCommand(this.oxaddr, 'GetUsers', soap).then((result) => {
  			try {
  				let d = result['data']['GetUsersResponse']['User'];
  				if (!Array.isArray(d)) {
  					result['data']['GetUsersResponse']['User'] = [d];
  				}
  			} catch (e) { }
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: createUsers(params[, callback])
  * - params:
  *   - User        | Array  | required |
  *     - Username  | string | required | Username
  *     - Password  | string | required | Password
  *     - UserLevel | string | required | Either "Administrator", "Operator", "User", or "Anonymous"
  *
  * {
  *   'User' : [
  *     {'Username': 'test1', 'Password' : 'password', 'UserLevel': 'Administrator'},
  *     {'Username': 'test2', 'Password' : 'password', 'UserLevel': 'Operator'},
  *     {'Username': 'test3', 'Password' : 'password', 'UserLevel': 'User'}
  *   ]
  * }
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.createUsers = function (params, callback) {
  	let promise = new Promise((resolve, reject) => {
  		let err_msg = '';
  		if (err_msg = mOnvifSoap.isInvalidValue(params, 'object')) {
  			reject(new Error('The value of "params" was invalid: ' + err_msg));
  			return;
  		}
  
  		if (err_msg = mOnvifSoap.isInvalidValue(params['User'], 'array')) {
  			reject(new Error('The "User" property was invalid: ' + err_msg));
  			return;
  		}
  
  		for (let i = 0; i < params['User'].length; i++) {
  			let u = params['User'][i];
  			if (err_msg = mOnvifSoap.isInvalidValue(u, 'object')) {
  				reject(new Error('The "User" property was invalid: ' + err_msg));
  				return;
  			}
  
  			if (err_msg = mOnvifSoap.isInvalidValue(u['Username'], 'string')) {
  				reject(new Error('The "Username" property was invalid: ' + err_msg));
  				return;
  			}
  
  			if (err_msg = mOnvifSoap.isInvalidValue(u['Password'], 'string')) {
  				reject(new Error('The "Password" property was invalid: ' + err_msg));
  				return;
  			}
  
  			if (err_msg = mOnvifSoap.isInvalidValue(u['UserLevel'], 'string')) {
  				reject(new Error('The "UserLevel" property was invalid: ' + err_msg));
  				return;
  			} else if (!u['UserLevel'].match(/^(Administrator|Operator|User|Anonymous)$/)) {
  				reject(new Error('The "UserLevel" property must be either "Administrator", "Operator", "User", or "Anonymous".'));
  				return;
  			}
  		}
  
  		let soap_body = '';
  		soap_body += '<tds:CreateUsers>';
  		params['User'].forEach((u) => {
  			soap_body += '<tds:User>';
  			soap_body += '<tt:Username>' + u['Username'] + '</tt:Username>';
  			soap_body += '<tt:Password>' + u['Password'] + '</tt:Password>';
  			soap_body += '<tt:UserLevel>' + u['UserLevel'] + '</tt:UserLevel>';
  			soap_body += '</tds:User>';
  		});
  		soap_body += '</tds:CreateUsers>';
  		let soap = this._createRequestSoap(soap_body);
  
  		mOnvifSoap.requestCommand(this.oxaddr, 'CreateUsers', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: deleteUsers(params[, callback])
  * - params:
  *   - User        | Array  | required |
  *     - Username  | string | required | Username
  *
  * {
  *   'User' : [
  *     {'Username': 'test1'},
  *     {'Username': 'test2'},
  *     {'Username': 'test3'}
  *   ]
  * }
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.deleteUsers = function (params, callback) {
  	let promise = new Promise((resolve, reject) => {
  		let err_msg = '';
  		if (err_msg = mOnvifSoap.isInvalidValue(params, 'object')) {
  			reject(new Error('The value of "params" was invalid: ' + err_msg));
  			return;
  		}
  
  		if (err_msg = mOnvifSoap.isInvalidValue(params['User'], 'array')) {
  			reject(new Error('The "User" property was invalid: ' + err_msg));
  			return;
  		}
  
  		for (let i = 0; i < params['User'].length; i++) {
  			let u = params['User'][i];
  			if (err_msg = mOnvifSoap.isInvalidValue(u, 'object')) {
  				reject(new Error('The "User" property was invalid: ' + err_msg));
  				return;
  			}
  
  			if (err_msg = mOnvifSoap.isInvalidValue(u['Username'], 'string')) {
  				reject(new Error('The "Username" property was invalid: ' + err_msg));
  				return;
  			}
  		}
  
  		let soap_body = '';
  		soap_body += '<tds:DeleteUsers>';
  		params['User'].forEach((u) => {
  			//soap_body += '<tds:User>';
  			soap_body += '<tt:Username>' + u['Username'] + '</tt:Username>';
  			//soap_body += '</tds:User>';
  		});
  		soap_body += '</tds:DeleteUsers>';
  		let soap = this._createRequestSoap(soap_body);
  
  		mOnvifSoap.requestCommand(this.oxaddr, 'DeleteUsers', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: setUser(params[, callback])
  * - params:
  *   - User        | Array  | required |
  *     - Username  | string | required | Username
  *     - Password  | string | optional | Password
  *     - UserLevel | string | optional | Either "Administrator", "Operator", "User", or "Anonymous"
  *
  * {
  *   'User' : [
  *     {'Username': 'test1', 'Password' : 'password', 'UserLevel': 'Administrator'},
  *     {'Username': 'test2', 'Password' : 'password', 'UserLevel': 'Operator'},
  *     {'Username': 'test3', 'Password' : 'password', 'UserLevel': 'User'}
  *   ]
  * }
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.setUser = function (params, callback) {
  	let promise = new Promise((resolve, reject) => {
  		let err_msg = '';
  		if (err_msg = mOnvifSoap.isInvalidValue(params, 'object')) {
  			reject(new Error('The value of "params" was invalid: ' + err_msg));
  			return;
  		}
  
  		if (err_msg = mOnvifSoap.isInvalidValue(params['User'], 'array')) {
  			reject(new Error('The "User" property was invalid: ' + err_msg));
  			return;
  		}
  
  		for (let i = 0; i < params['User'].length; i++) {
  			let u = params['User'][i];
  			if (err_msg = mOnvifSoap.isInvalidValue(u, 'object')) {
  				reject(new Error('The "User" property was invalid: ' + err_msg));
  				return;
  			}
  
  			if (err_msg = mOnvifSoap.isInvalidValue(u['Username'], 'string')) {
  				reject(new Error('The "Username" property was invalid: ' + err_msg));
  				return;
  			}
  
  			if ('Password' in u) {
  				if (err_msg = mOnvifSoap.isInvalidValue(u['Password'], 'string')) {
  					reject(new Error('The "Password" property was invalid: ' + err_msg));
  					return;
  				}
  			}
  
  			if ('UserLevel' in u) {
  				if (err_msg = mOnvifSoap.isInvalidValue(u['UserLevel'], 'string')) {
  					reject(new Error('The "UserLevel" property was invalid: ' + err_msg));
  					return;
  				} else if (!u['UserLevel'].match(/^(Administrator|Operator|User|Anonymous)$/)) {
  					reject(new Error('The "UserLevel" property must be either "Administrator", "Operator", "User", or "Anonymous".'));
  					return;
  				}
  			}
  		}
  
  		let soap_body = '';
  		soap_body += '<tds:SetUser>';
  		params['User'].forEach((u) => {
  			soap_body += '<tds:User>';
  			soap_body += '<tt:Username>' + u['Username'] + '</tt:Username>';
  			if ('Password' in u) {
  				soap_body += '<tt:Password>' + u['Password'] + '</tt:Password>';
  			}
  			if ('UserLevel' in u) {
  				soap_body += '<tt:UserLevel>' + u['UserLevel'] + '</tt:UserLevel>';
  			}
  			soap_body += '</tds:User>';
  		});
  		soap_body += '</tds:SetUser>';
  		let soap = this._createRequestSoap(soap_body);
  
  		mOnvifSoap.requestCommand(this.oxaddr, 'SetUser', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: getRelayOutputs([callback])
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.getRelayOutputs = function (callback) {
  	let promise = new Promise((resolve, reject) => {
  		let soap_body = '<tds:GetRelayOutputs/>';
  		let soap = this._createRequestSoap(soap_body);
  		mOnvifSoap.requestCommand(this.oxaddr, 'GetRelayOutputs', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: getNTP([callback])
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.getNTP = function (callback) {
  	let promise = new Promise((resolve, reject) => {
  		let soap_body = '<tds:GetNTP/>';
  		let soap = this._createRequestSoap(soap_body);
  		mOnvifSoap.requestCommand(this.oxaddr, 'GetNTP', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: setNTP(params[, callback])
  * - params:
  *   - FromDHCP      | Boolean | required | true or false
  *   - NTPManual     | Object  | optional |
  *     - Type        | String  | required | "IPv4" or "IPv6"
  *     - IPv4Address | String  | required | IP address
  *
  * {
  *    "FromDHCP": "false",
  *    "NTPManual": {"Type": "IPv4", "IPv4Address": "192.168.10.1"}
  * }
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.setNTP = function (params, callback) {
  	let promise = new Promise((resolve, reject) => {
  		let err_msg = '';
  		if (err_msg = mOnvifSoap.isInvalidValue(params, 'object')) {
  			reject(new Error('The value of "params" was invalid: ' + err_msg));
  			return;
  		}
  
  		if (err_msg = mOnvifSoap.isInvalidValue(params['FromDHCP'], 'boolean')) {
  			reject(new Error('The "FromDHCP" property was invalid: ' + err_msg));
  			return;
  		}
  
  		if ('NTPManual' in params) {
  			if (err_msg = mOnvifSoap.isInvalidValue(params['NTPManual'], 'object')) {
  				reject(new Error('The "NTPManual" property was invalid: ' + err_msg));
  				return;
  			}
  
  			let o = params['NTPManual'];
  
  			let type = o['Type'];
  			if (err_msg = mOnvifSoap.isInvalidValue(type, 'string')) {
  				reject(new Error('The "Type" property was invalid: ' + err_msg));
  				return;
  			} else if (!type.match(/^(IPv4|IPv6)$/)) {
  				reject(new Error('The "Type" property was invalid: The value must be either "IPv4" or "IPv6".'));
  				return;
  			}
  
  			if (type === 'IPv4') {
  				if (err_msg = mOnvifSoap.isInvalidValue(o['IPv4Address'], 'string')) {
  					reject(new Error('The "IPv4Address" property was invalid: ' + err_msg));
  					return;
  				}
  			} else if (type === 'IPv6') {
  				if (err_msg = mOnvifSoap.isInvalidValue(o['IPv6Address'], 'string')) {
  					reject(new Error('The "IPv6Address" property was invalid: ' + err_msg));
  					return;
  				}
  			}
  		}
  
  		let soap_body = '';
  		soap_body += '<tds:SetNTP>';
  		soap_body += '<tds:FromDHCP>' + params['FromDHCP'] + '</tds:FromDHCP>';
  		if ('NTPManual' in params) {
  			let o = params['NTPManual'];
  			soap_body += '<tds:NTPManual>';
  			soap_body += '<tt:Type>' + o['Type'] + '</tt:Type>';
  			if (o['Type'] === 'IPv4') {
  				soap_body += '<tt:IPv4Address>' + o['IPv4Address'] + '</tt:IPv4Address>';
  			} else {
  				soap_body += '<tt:IPv6Address>' + o['IPv6Address'] + '</tt:IPv6Address>';
  			}
  			soap_body += '</tds:NTPManual>';
  		}
  		soap_body += '</tds:SetNTP>';
  		let soap = this._createRequestSoap(soap_body);
  
  		mOnvifSoap.requestCommand(this.oxaddr, 'SetNTP', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: getDynamicDNS([callback])
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.getDynamicDNS = function (callback) {
  	let promise = new Promise((resolve, reject) => {
  		let soap_body = '<tds:GetDynamicDNS/>';
  		let soap = this._createRequestSoap(soap_body);
  		mOnvifSoap.requestCommand(this.oxaddr, 'GetDynamicDNS', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: getZeroConfiguration([callback])
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.getZeroConfiguration = function (callback) {
  	let promise = new Promise((resolve, reject) => {
  		let soap_body = '<tds:GetZeroConfiguration/>';
  		let soap = this._createRequestSoap(soap_body);
  		mOnvifSoap.requestCommand(this.oxaddr, 'GetZeroConfiguration', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: getIPAddressFilter([callback])
  * No devcie I own supports this method for now.
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.getIPAddressFilter = function (callback) {
  	let promise = new Promise((resolve, reject) => {
  		let soap_body = '<tds:GetIPAddressFilter/>';
  		let soap = this._createRequestSoap(soap_body);
  		mOnvifSoap.requestCommand(this.oxaddr, 'GetIPAddressFilter', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: setIPAddressFilter(params[, callback])
  * - params:
  *   - Type:          | String  | required | 'Allow', 'Deny' 
  *   - IPv4Address    | Array   | required | 
  *     - Address      | String  | required | IPv4 address
  *     - PrefixLength | Integer | required | Prefix/submask length
  *
  * {
  *    "Type": "Allow",
  *    "IPv4Address": [
  *      {
  *        "Address": "192.168.10.3",
  *        "PrefixLength": 24
  *      }
  *    ]
  * ]}
  *
  * No devcie I own supports this method for now.
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.setIPAddressFilter = function (params, callback) {
  	let promise = new Promise((resolve, reject) => {
  		let err_msg = '';
  		if (err_msg = mOnvifSoap.isInvalidValue(params, 'object')) {
  			reject(new Error('The value of "params" was invalid: ' + err_msg));
  			return;
  		}
  
  		if (err_msg = mOnvifSoap.isInvalidValue(params['Type'], 'string')) {
  			reject(new Error('The "Type" property was invalid: ' + err_msg));
  			return;
  		} else if (!params['Type'].match(/^(Allow|Deny)$/)) {
  			reject(new Error('The "Type" property was invalid: The value must be either "Allow" or "Deny".'));
  			return;
  		}
  
  		if (err_msg = mOnvifSoap.isInvalidValue(params['IPv4Address'], 'array')) {
  			reject(new Error('The "IPv4Address" property was invalid: ' + err_msg));
  			return;
  		}
  
  		for (let i = 0; i < params['IPv4Address'].length; i++) {
  			let o = params['IPv4Address'][i];
  			if (err_msg = mOnvifSoap.isInvalidValue(o, 'object')) {
  				reject(new Error('The "IPv4Address" property was invalid: ' + err_msg));
  				return;
  			}
  			if (err_msg = mOnvifSoap.isInvalidValue(o['Address'], 'string')) {
  				reject(new Error('The "Address" property was invalid: ' + err_msg));
  				return;
  			} else if (!o['Address'].match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)) {
  				reject(new Error('The "Address" property was invalid as a IPv4 address.'));
  				return;
  			}
  			if (err_msg = mOnvifSoap.isInvalidValue(o['PrefixLength'], 'integer')) {
  				reject(new Error('The "PrefixLength" property was invalid: ' + err_msg));
  				return;
  			}
  		}
  
  		let soap_body = '';
  		soap_body += '<tds:SetIPAddressFilter>';
  		soap_body += '<tds:IPAddressFilter>';
  		soap_body += '<tt:Type>' + params['Type'] + '</tt:Type>';
  		params['IPv4Address'].forEach((o) => {
  			soap_body += '<tt:IPv4Address>';
  			soap_body += '<tt:Address>' + o['Address'] + '</tt:Address>';
  			soap_body += '<tt:PrefixLength>' + o['PrefixLength'] + '</tt:PrefixLength>';
  			soap_body += '</tt:IPv4Address>';
  		});
  		soap_body += '</tds:IPAddressFilter>';
  		soap_body += '</tds:SetIPAddressFilter>';
  		let soap = this._createRequestSoap(soap_body);
  
  		mOnvifSoap.requestCommand(this.oxaddr, 'SetIPAddressFilter', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: getServices(params[, callback])
  * - params:
  *   - IncludeCapability | boolean | required | true or false
  *
  * {'IncludeCapability': false}
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.getServices = function (params, callback) {
  	let promise = new Promise((resolve, reject) => {
  		let err_msg = '';
  		if (err_msg = mOnvifSoap.isInvalidValue(params, 'object')) {
  			reject(new Error('The value of "params" was invalid: ' + err_msg));
  			return;
  		}
  
  		if (err_msg = mOnvifSoap.isInvalidValue(params['IncludeCapability'], 'boolean')) {
  			reject(new Error('The "IncludeCapability" property was invalid: ' + err_msg));
  			return;
  		}
  
  		let soap_body = '';
  		soap_body += '<tds:GetServices>';
  		soap_body += '<tds:IncludeCapability>' + params['IncludeCapability'] + '</tds:IncludeCapability>';
  		soap_body += '</tds:GetServices>';
  		let soap = this._createRequestSoap(soap_body);
  
  		mOnvifSoap.requestCommand(this.oxaddr, 'GetServices', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  /* ------------------------------------------------------------------
  * Method: getServiceCapabilities([callback])
  * ---------------------------------------------------------------- */
  OnvifServiceDevice.prototype.getServiceCapabilities = function (callback) {
  	let promise = new Promise((resolve, reject) => {
  		let soap_body = '<tds:GetServiceCapabilities/>';
  		let soap = this._createRequestSoap(soap_body);
  		mOnvifSoap.requestCommand(this.oxaddr, 'GetServiceCapabilities', soap).then((result) => {
  			resolve(result);
  		}).catch((error) => {
  			reject(error);
  		});
  	});
  	if (callback) {
  		promise.then((result) => {
  			callback(null, result);
  		}).catch((error) => {
  			callback(error);
  		});
  	} else {
  		return promise;
  	}
  };
  
  module.exports = OnvifServiceDevice;