diff --git a/config/dev/default.toml b/config/dev/default.toml index d7a52db..bc956c6 100644 --- a/config/dev/default.toml +++ b/config/dev/default.toml @@ -16,18 +16,23 @@ accept_untrusted_certs = true [settings] max_fails = 3 -send_voice = true +send_voice = false send_gps = true -send_group_monitoring_before_each_call = false -gps_report_interval = 10000 # The interval in which the GPS is sent +send_group_monitoring_before_each_call = 'true' +gps_report_interval = 300 # The interval in which the GPS is sent gps_lat_start_point = 46.217802 gps_lng_start_point = 24.776126 delay_between_clients = 300 testing_duration = 600 # seconds [assets] + +#ids = [ +# 5488, 5489, 5490, 5491, 5492, 5493, 5494, 5495, 5496, 5497, 5498, 5499, 5500, 5501, 5502 +#] + ids = [ - 5488 + 1868 ] [sounds] diff --git a/src/asset.js b/src/asset.js index 41aaccd..f498852 100644 --- a/src/asset.js +++ b/src/asset.js @@ -1,605 +1,605 @@ -const utils = require('./utils'); -const Request = require('request'); -const async = require('async'); -const io = require('socket.io-client'); -const Mumble = require('./mumble'); -const currentPath = require('path').dirname(require.main.filename); -const fs = require('fs'); -const lame = require('lame'); -const Speaker = require('speaker'); -const Resampler = require('libsamplerate.js'); -const chunker = require('stream-chunker'); -const Transform = require('stream').Transform; -const Throttle = require('stream-throttle').Throttle; -const chalk = require('chalk'); -const OpusEncoder = require('node-opus').OpusEncoder; - -const log = require('./utils').log - -class Asset { - - constructor(id, configs, token) { - - this.id = id; - this.token = token; - this.configs = configs - this._processConfigs(); - this.pttEndSuccessfully = false; - this.assetProps = {}; - this.murmurPassword = ''; - this.startTime = null; // Will be set at the initialization of ptt(_start -> on_tt) - this.endTime = +new Date() + (this.configs.settings.testing_duration * 1000) - - utils.writeLog(`Creating asset ${id}`) - - // Do async work: Init asset. - async.waterfall([ - this._getDataFromApi.bind(this), - this._connectToHub.bind(this), - this._connectToMurmur.bind(this), - this._register.bind(this), - this._moveToChannel.bind(this), - ], - (err, result) => { - utils.writeLog(`Asset ${this.id} was successfully initialized`) - - if (err) { - console.log('err', err) - utils.writeLog(`Asset ${this.id} was not successfully initialized`, err); - return; - } - - // Start simulating... - this._start(); - }); - } - - _processConfigs() { - let apiConfig = this.configs.api; - this.apiEndpoint = apiConfig.use_secure ? 'https' : 'http'; - this.apiEndpoint += '://' + apiConfig.host + (apiConfig.port ? (':' + apiConfig.port) : ''); - } - - _getDataFromApi(callback) { - utils.writeLog(`Get informations about asset ${this.id}`); - Request.get( - this.apiEndpoint + '/asset/' + this.id, - { - headers: { - Authorization: `Bearer ${this.token}` - } - }, - (error, response, body) => { - if (!error && (response.statusCode === 200 || response.statusCode === 201)) { - let bodyObj = JSON.parse(body); - // Here are the asset fields. - this.assetProps = bodyObj.data; - // console.log('!!!!!!!!!!!!!!!!!!!!!!!!!!!!', this.assetProps) - return this._getMumblePassword(callback); - } else { - utils.writeLog(`ERROR getting informations about asset ${this.id}`, error); - utils.writeErrorLog(`ERROR_API`); - return callback(error); - } - } - ); - } - - _getMumblePassword(callback) { - utils.writeLog(`Get Mumble password for asset ${this.id}`); - Request.get( - this.apiEndpoint + '/asset/' + this.id + '/account', - { - headers: { - Authorization: `Bearer ${this.token}` - } - }, - (error, response, body) => { - if (!error && (response.statusCode === 200 || response.statusCode === 201)) { - let bodyObj = JSON.parse(body); - - // Here are the asset fields. - this.murmurPassword = bodyObj.data.configuration.mumble_password; - // console.log('!!!!!!!!!!!!!!!!!!!!!!!!!!!!', this.assetProps) - return this._getGroupsFromApi(callback); - } else { - utils.writeLog(`ERROR getting informations about asset ${this.id}`, error); - utils.writeErrorLog(`ERROR_API`); - return callback(error); - } - } - ); - } - - _getGroupsFromApi(callback) { - - Request.get( - this.apiEndpoint + '/asset/ ' + this.id + '/groups', - { - headers: { - Authorization: `Bearer ${this.token}` - } - }, - (error, response, body) => { - if (!error && (response.statusCode === 200 || response.statusCode === 201)) { - - let bodyObj = JSON.parse(body); - - this.groups = bodyObj.data; - - // Find what group this asset is monitoring. - this.groups.forEach((g) => { - if (g.is_talk_group && g.monitoring.indexOf(this.id) != -1) { - this.groupId = g.id; - this.groupSipId = g.sip_id; - this.groupName = g.name; - } - }); - - if (!this.groupId) { - return callback('No talk group assigned to ' + this.id); - } - utils.writeLog(`Informations about asset ${this.id} received`) - return callback(); - } else { - utils.writeLog(`Error getting informations about asset ${this.id}`, error); - utils.writeErrorLog(`ERROR_API`); - return callback(error); - } - } - ); - } - - _connectToHub(callback) { - - let hubAddress = this.configs.hub.address; - let options = {rejectUnauthorized: false, secure: true}; - let hub = io(hubAddress, options); - this.hub = hub; - - // Disconnect event - hub.on('disconnect', () => { - utils.writeLog(`Asset ${this.id} disconnected from HUB`); - }); - - hub.on('connect_timeout', () => { - utils.writeLog(`Asset ${this.id} connect_timeout from HUB`); - utils.writeErrorLog(`ERROR_HUB`); - }); - - hub.on('connect_error', () => { - utils.writeLog(`Asset ${this.id} connect_error from HUB`); - utils.writeErrorLog(`ERROR_HUB`); - }); - - hub.once('connect', () => { - return callback(); - }); - hub.on('connect', () => { - utils.writeLog(`Asset ${this.id} connected to HUB`); - this._sendArs(); - }); - - hub.on('ptt-press', data => { - // this._log('ptt-press received'); - }); - - hub.on('ptt-release', data => { - // this._log('ptt-release received'); - }); - - hub.on('reload', data => { - // this._log('reload received'); - }); - } - - _sendArs() { - let hub = this.hub; - if (hub && hub.connected) { - utils.writeLog(`Asset ${this.id} sending ARS`) - hub.emit('ars', JSON.stringify({ - ars: true, - userAgent: 'android', - asset_id: this.id, - account_id: this.assetProps.account_id, - asset_sip_id : this.assetProps.sip_id, - fake: false - })); - } - } - - _connectToMurmur(callback) { - this.configs.murmurPassword = this.murmurPassword; - - this.mumble = new Mumble(this.id, this.configs, (err) => { - if (err) { - utils.writeLog(`Asset ${this.id} Murmur connection error`, err); - utils.writeErrorLog(`ERROR_MURMUR`); - return callback(err); - } else { - // return callback(); - setTimeout(()=> { - return callback(); - }, 400); - } - }); - } - - _register(callback) { - Request.post( - this.apiEndpoint + '/audio/register/' + this.id, - { - headers: { - Authorization: `Bearer ${this.token}` - } - }, - (error, response, body) => { - if (!error && (response.statusCode === 200 || response.statusCode === 201)) { - utils.writeLog(`Asset ${this.id} audio registered`); - return callback(); - } else { - console.log('_register--------------------') - console.log('error---', error) - console.log('response---', response) - console.log('body---', body) - // utils.writeLog(`Asset ${this.id} audio registered error`, error); - // utils.writeErrorLog(`ERROR_API`); - // return callback(error); - } - } - ); - } - - _moveToChannel(callback) { - console.log('_moveToChannel') - Request.post( - this.apiEndpoint + '/audio/enter-group/' + this.id + '/' + this.groupId, - { - headers: { - Authorization: `Bearer ${this.token}` - } - }, - (error, response, body) => { - if (!error && (response.statusCode === 200 || response.statusCode === 201)) { - let hub = this.hub; - if (hub && hub.connected) { - - hub.emit('group-monitoring', JSON.stringify( - { - asset_id: this.id, - asset_sip_id : this.assetProps.sip_id, - asset_alias : this.assetProps.name, - request_ptt_groups_status: false, - group_sip_id: this.groupSipId, - group_id: this.groupId, - group_name: this.groupName, - scan_group_ids: null - } - )); - utils.writeLog(`Asset ${this.id} mmonitoring group ${this.groupId}`) - if (callback) return callback(); - } else { - if (callback) return callback('Cannot send group-monitoring: Hub not connected'); - } - } else { - console.log('_moveToChannel--------------------') - console.log('error---', error) - console.log('response---', response) - console.log('body---', body) - // utils.writeLog(`Asset ${this.id} audio enter group error`, error); - // utils.writeErrorLog(`ERROR_API`); - // return callback(error); - } - } - ); - } - - _start() { - this.startTime = +new Date(); - if(this.endTime > this.startTime) { - if(this.configs.settings.send_voice) { - this._makePtt(() => { - if(this.pttEndSuccessfully) { - this._verifyRecorder(); - } - }); - } - - if(this.configs.settings.send_gps) { - this._sendGPS(); - } - } else { - const assetIds = this.configs.assets.ids; - if(Math.max(...assetIds) == this.id) { - setTimeout(() => { - utils.writeLog('STOP') - .then(() => { - process.exit(0); - }); - }, 10000); - } - } - - } - - _verifyRecorder(callback) { - console.log(chalk.green(`Check to see if a record was created for the unit id: ${this.id} | name: ${this.assetProps.name}`)); - let startDate = parseInt(this.startTime) - 3000; - let stopDate = parseInt(+new Date() + 30 * 1000); - setTimeout(() => { this._getRecord(startDate, stopDate, this.configs); }, 3000); - } - - _getRecord(startDate, stopDate, configs) { - - - console.log(`${this.apiEndpoint}/asset-history/${this.id}/call-history/${startDate}/${stopDate}/10`); - - // console.log(this.apiEndpoint + `/history-pagination/1/300/${this.id}/${startDate}/${stopDate}/0/0/0/1/null`) - Request.get( - `${this.apiEndpoint}/asset-history/${this.id}/call-history/${startDate}/${stopDate}/10`, - // this.apiEndpoint + `/history-pagination/1/300/${this.id}/${startDate}/${stopDate}/0/0/0/1/null`, - { - headers: { - Authorization: `Bearer ${this.token}` - } - }, - (error, response, body) => { - if(!error && (response.statusCode === 200 || response.statusCode === 201)) { - let bodyObj = JSON.parse(body); - // console.log('bodyObj', bodyObj) - if(bodyObj.data.length > 0) { - - // Get the latest record - let latest = { id: 0 } - bodyObj.data.forEach(e => { - if(e.id > latest.id) { - latest = e; - } - }) - console.log(chalk.green(`[RECORDER] Record found(${latest.id}) for asset ${this.id} ✓`)); - utils.writeLog(`[RECORDER] Record found(${latest.id}) for asset ${this.id}`) - } else { - console.log(chalk.yellow(`[RECORDER] for asset ${this.id} not found`)); - } - } else { - utils.writeLog(`Error getting record for asset ${this.id} | ${error}`); - utils.writeErrorLog(`ERROR_RECORDER`); - } - let assetIds = configs.assets.ids; - - this._start(); - - } - ); - } - - _makePtt(callback) { - // Sending group monitoring before we make PTT() - // Is used for the HUB load test(_moveToChannel is sent anyway after connecting to murmur-register) - if (this.configs.settings.send_group_monitoring_before_each_call == 'true') { - this._moveToChannel(); - } - - // Check hub. - let hub = this.hub; - if (!hub || !hub.connected) { - return callback(); - } - - // Send ptt-press and wait for it to be accepted. - this._sendPttPress((isAccepted) => { - - if (!isAccepted) { - return callback(); - } - - // Select a random track from sounds.tracks - this.soundPath = `${currentPath}/sounds/${Math.floor(Math.random() * (this.configs.sounds.tracks.length) + 1)}.mp3` - - // Ptt accepted. We can send voice. - // Mp3 read stream. - var mp3 = fs.createReadStream(this.soundPath); - mp3.on('error', (e) => { - utils.writeErrorLog(`Error _makePtt asset ${this.id}`, JSON.stringify(e)); - }); - - mp3.on('data', (data) => { - - }); - - // Decoded mp3. - var lameDecoder = mp3 - .pipe(new lame.Decoder) - .on('data', (data) => { - - }) - - // On format we continue pipeing in. We need the mp3 format to know what to do next. Eg: To resample the mp3 we need to know the current sample rate. - .on('format', (format) => { - - // Create resampler. - var resampler = new Resampler({ - unsafe: false, - type: Resampler.Type.ZERO_ORDER_HOLD, - ratio: 48000 / format.sampleRate, - channels: format.channels - }) - - // Create mumble voice stream. - var voiceStream = this.mumble.client.createVoiceStream(0, format.channels); - - // Send ptt-release on voice end. - voiceStream.on('end', () => { - this.pttEndSuccessfully = true; - setTimeout(() => { - this._sendPttRelease(); - callback(); - }, 1500); // Hangtime - }); - - // @TODO: Ugly hack for the voice to work with mp3 (The fix is to remove float32ToInt16 conversion). It is the _transform function from OpusEncoderStream class (mumble-client-codecs-node project). - voiceStream._readableState.pipes._transform = function(chunk, encoding, callback) { - if (!this._opus) { - this._opus = new OpusEncoder(48000, chunk.numberOfChannels); - } - - callback(null, { - target: chunk.target, - codec: 'Opus', - frame: this._opus.encode(chunk.pcm), - position: chunk.position - }); - } - - // This is used to slow down the mp3 transmision. Murmur will drop packages that are too early. This will make the mp3 transmision real time, just like it is a microphone stream. - var throtler = new Transform({ - transform (data, _, callback) { - setTimeout(() => { - callback(null, data) - }, 16 / format.channels) - }, - readableObjectMode: true - }); - - // Used for converting buffer to float32. Needed for Murmur. - const buffer2Float32Array = new Transform({ - transform (data, _, callback) { - callback(null, new Float32Array(data.buffer, data.byteOffset, data.byteLength / 4)) - }, - readableObjectMode: true - }); - - // Pipe on. - lameDecoder - - // Make 4096 chunks to be like the microphone stream. - // .pipe(chunker(4096, {flush: true, align: true})) - - // Resample to 48000 hz. - .pipe(resampler) - - // Make 4 * 480 chunks for Murmur purposes. - .pipe(chunker(4 * 480, {flush: true, align: true})) - - // Slow down the stream. - .pipe(throtler) - - // Convert to float 32. - .pipe(buffer2Float32Array) - - // Used for testing in nodejs speaker. - // .pipe(new Speaker) - - // Mumble voice stream. - .pipe(voiceStream); - }); - }) - } - - _sendGPS(callback) { - let hub = this.hub; - if (hub && hub.connected) { - - let lat = this.configs.settings.gps_lat_start_point; - let lng = this.configs.settings.gps_lng_start_point; - - setTimeout(() => { - var new_lat = this._randomCoordinates(lat); - var new_lng = this._randomCoordinates(lng); - - hub.emit('gps', JSON.stringify( - { - unix_time: 1467126677000, - asset_id: this.assetProps.id, - asset_sip_id: this.assetProps.sip_id, - speed_kmh: 16, - lat: new_lat, - lng: new_lng, - accuracy: 20.3, - activity_type: "driving", - activity_confidence: 90 - } - )); - lat = new_lat; - lng = new_lng; - - }, this.configs.settings.gps_report_interval); - } - } - - _randomCoordinates(coordinate) { - if(Math.round(Math.random()) === 0) { - return parseFloat(parseFloat(coordinate) - parseFloat((Math.random() * (0.01 - 0.005) + 0.005).toFixed(4))).toFixed(6); - } else { - return parseFloat(parseFloat(coordinate) + parseFloat((Math.random() * (0.01 - 0.005) + 0.005).toFixed(4))).toFixed(6); - } - } - - _sendPttPress(callback) { - let hub = this.hub; - if (hub && hub.connected) { - - var pttAcceptHandler = (data) => { - data = JSON.parse(data); - if (data.asset_id != this.id) { - return; - } - utils.writeLog(`Asset ${this.id} sending PTT-PRESS to group ${this.groupId}`) - hub.removeListener('ptt-deny', pttDenyHandler); - callback(true); - }; - hub.once('ptt-accept', pttAcceptHandler); - - var pttDenyHandler = (data) => { - data = JSON.parse(data); - if (data.asset_id != this.id) { - return; - } - utils.writeLog(`Asset ${this.id} received PTT-DENY`); - hub.removeListener('ptt-accept', pttAcceptHandler); - callback(false); - }; - hub.once('ptt-deny', pttDenyHandler); - - hub.emit('ptt-press', JSON.stringify( - { - destination_group_id: this.groupId, - destination_group_sip_id: this.groupSipId, - destination_asset_id: 0, - destination_asset_sip_id: 0, - asset_id: this.id, - asset_sip_id: this.assetProps.sip_id, - asset_alias : this.assetProps.name, - priority : this.assetProps.priority, - portable_asset_id: 0, - portable_alias: null, - } - )); - } - } - - _sendPttRelease() { - let hub = this.hub; - if (hub && hub.connected) { - - utils.writeLog(`Asset ${this.id} sending PTT-RELEASE to group ${this.groupId}`); - hub.emit('ptt-release', JSON.stringify( - { - destination_group_id: this.groupId, - destination_group_sip_id: this.groupSipId, - destination_asset_id: 0, - destination_asset_sip_id: 0, - asset_id: this.id, - asset_sip_id: this.assetProps.sip_id, - asset_alias : this.assetProps.name, - priority : this.assetProps.priority, - portable_asset_id: 0, - portable_alias: null, - } - )); - } - } - -} - +const utils = require('./utils'); +const Request = require('request'); +const async = require('async'); +const io = require('socket.io-client'); +const Mumble = require('./mumble'); +const currentPath = require('path').dirname(require.main.filename); +const fs = require('fs'); +const lame = require('lame'); +const Speaker = require('speaker'); +const Resampler = require('libsamplerate.js'); +const chunker = require('stream-chunker'); +const Transform = require('stream').Transform; +const Throttle = require('stream-throttle').Throttle; +const chalk = require('chalk'); +const OpusEncoder = require('node-opus').OpusEncoder; + +const log = require('./utils').log + +class Asset { + + constructor(id, configs, token) { + + this.id = id; + this.token = token; + this.configs = configs + this._processConfigs(); + this.pttEndSuccessfully = false; + this.assetProps = {}; + this.murmurPassword = ''; + this.startTime = null; // Will be set at the initialization of ptt(_start -> on_tt) + this.endTime = +new Date() + (this.configs.settings.testing_duration * 1000) + + utils.writeLog(`Creating asset ${id}`) + + // Do async work: Init asset. + async.waterfall([ + this._getDataFromApi.bind(this), + this._connectToHub.bind(this), + this._connectToMurmur.bind(this), + this._register.bind(this), + this._moveToChannel.bind(this), + ], + (err, result) => { + utils.writeLog(`Asset ${this.id} was successfully initialized`) + + if (err) { + console.log('err', err) + utils.writeLog(`Asset ${this.id} was not successfully initialized`, err); + return; + } + + // Start simulating... + this._start(); + }); + } + + _processConfigs() { + let apiConfig = this.configs.api; + this.apiEndpoint = apiConfig.use_secure ? 'https' : 'http'; + this.apiEndpoint += '://' + apiConfig.host + (apiConfig.port ? (':' + apiConfig.port) : ''); + } + + _getDataFromApi(callback) { + utils.writeLog(`Get informations about asset ${this.id}`); + Request.get( + this.apiEndpoint + '/asset/' + this.id, + { + headers: { + Authorization: `Bearer ${this.token}` + } + }, + (error, response, body) => { + if (!error && (response.statusCode === 200 || response.statusCode === 201)) { + let bodyObj = JSON.parse(body); + // Here are the asset fields. + this.assetProps = bodyObj.data; + // console.log('!!!!!!!!!!!!!!!!!!!!!!!!!!!!', this.assetProps) + return this._getMumblePassword(callback); + } else { + utils.writeLog(`ERROR getting informations about asset ${this.id}`, error); + utils.writeErrorLog(`ERROR_API`); + return callback(error); + } + } + ); + } + + _getMumblePassword(callback) { + utils.writeLog(`Get Mumble password for asset ${this.id}`); + Request.get( + this.apiEndpoint + '/asset/' + this.id + '/account', + { + headers: { + Authorization: `Bearer ${this.token}` + } + }, + (error, response, body) => { + if (!error && (response.statusCode === 200 || response.statusCode === 201)) { + let bodyObj = JSON.parse(body); + + // Here are the asset fields. + this.murmurPassword = bodyObj.data.configuration.mumble_password; + // console.log('!!!!!!!!!!!!!!!!!!!!!!!!!!!!', this.assetProps) + return this._getGroupsFromApi(callback); + } else { + utils.writeLog(`ERROR getting informations about asset ${this.id}`, error); + utils.writeErrorLog(`ERROR_API`); + return callback(error); + } + } + ); + } + + _getGroupsFromApi(callback) { + + Request.get( + this.apiEndpoint + '/asset/ ' + this.id + '/groups', + { + headers: { + Authorization: `Bearer ${this.token}` + } + }, + (error, response, body) => { + if (!error && (response.statusCode === 200 || response.statusCode === 201)) { + + let bodyObj = JSON.parse(body); + + this.groups = bodyObj.data; + + // Find what group this asset is monitoring. + this.groups.forEach((g) => { + if (g.is_talk_group && g.monitoring.indexOf(this.id) != -1) { + this.groupId = g.id; + this.groupSipId = g.sip_id; + this.groupName = g.name; + } + }); + + if (!this.groupId) { + return callback('No talk group assigned to ' + this.id); + } + utils.writeLog(`Informations about asset ${this.id} received`) + return callback(); + } else { + utils.writeLog(`Error getting informations about asset ${this.id}`, error); + utils.writeErrorLog(`ERROR_API`); + return callback(error); + } + } + ); + } + + _connectToHub(callback) { + + let hubAddress = this.configs.hub.address; + let options = {rejectUnauthorized: false, secure: true}; + let hub = io(hubAddress, options); + this.hub = hub; + + // Disconnect event + hub.on('disconnect', () => { + utils.writeLog(`Asset ${this.id} disconnected from HUB`); + }); + + hub.on('connect_timeout', () => { + utils.writeLog(`Asset ${this.id} connect_timeout from HUB`); + utils.writeErrorLog(`ERROR_HUB`); + }); + + hub.on('connect_error', () => { + utils.writeLog(`Asset ${this.id} connect_error from HUB`); + utils.writeErrorLog(`ERROR_HUB`); + }); + + hub.once('connect', () => { + return callback(); + }); + hub.on('connect', () => { + utils.writeLog(`Asset ${this.id} connected to HUB`); + this._sendArs(); + }); + + hub.on('ptt-press', data => { + // this._log('ptt-press received'); + }); + + hub.on('ptt-release', data => { + // this._log('ptt-release received'); + }); + + hub.on('reload', data => { + // this._log('reload received'); + }); + } + + _sendArs() { + let hub = this.hub; + if (hub && hub.connected) { + utils.writeLog(`Asset ${this.id} sending ARS`) + hub.emit('ars', JSON.stringify({ + ars: true, + userAgent: 'android', + asset_id: this.id, + account_id: this.assetProps.account_id, + asset_sip_id : this.assetProps.sip_id, + fake: false + })); + } + } + + _connectToMurmur(callback) { + this.configs.murmurPassword = this.murmurPassword; + + this.mumble = new Mumble(this.id, this.configs, (err) => { + if (err) { + utils.writeLog(`Asset ${this.id} Murmur connection error`, err); + utils.writeErrorLog(`ERROR_MURMUR`); + return callback(err); + } else { + // return callback(); + setTimeout(()=> { + return callback(); + }, 400); + } + }); + } + + _register(callback) { + Request.post( + this.apiEndpoint + '/audio/register/' + this.id, + { + headers: { + Authorization: `Bearer ${this.token}` + } + }, + (error, response, body) => { + if (!error && (response.statusCode === 200 || response.statusCode === 201)) { + utils.writeLog(`Asset ${this.id} audio registered`); + return callback(); + } else { + console.log('_register--------------------') + console.log('error---', error) + console.log('response---', response) + console.log('body---', body) + // utils.writeLog(`Asset ${this.id} audio registered error`, error); + // utils.writeErrorLog(`ERROR_API`); + // return callback(error); + } + } + ); + } + + _moveToChannel(callback) { + Request.post( + this.apiEndpoint + '/audio/enter-group/' + this.id + '/' + this.groupId, + { + headers: { + Authorization: `Bearer ${this.token}` + } + }, + (error, response, body) => { + if (!error && (response.statusCode === 200 || response.statusCode === 201)) { + let hub = this.hub; + if (hub && hub.connected) { + + hub.emit('group-monitoring', JSON.stringify( + { + asset_id: this.id, + asset_sip_id : this.assetProps.sip_id, + asset_alias : this.assetProps.name, + request_ptt_groups_status: false, + group_sip_id: this.groupSipId, + group_id: this.groupId, + group_name: this.groupName, + scan_group_ids: null + } + )); + utils.writeLog(`Asset ${this.id} mmonitoring group ${this.groupId}`) + if (callback) return callback(); + } else { + if (callback) return callback('Cannot send group-monitoring: Hub not connected'); + } + } else { + console.log('_moveToChannel--------------------') + console.log('error---', error) + console.log('response---', response) + console.log('body---', body) + // utils.writeLog(`Asset ${this.id} audio enter group error`, error); + // utils.writeErrorLog(`ERROR_API`); + // return callback(error); + } + } + ); + } + + _start() { + this.startTime = +new Date(); + if(this.endTime > this.startTime) { + if(this.configs.settings.send_voice) { + this._makePtt(() => { + if(this.pttEndSuccessfully) { + this._verifyRecorder(); + } + }); + } + + if(this.configs.settings.send_gps) { + this._sendGPS(); + } + } else { + const assetIds = this.configs.assets.ids; + if(Math.max(...assetIds) == this.id) { + setTimeout(() => { + utils.writeLog('STOP') + .then(() => { + process.exit(0); + }); + }, 10000); + } + } + + } + + _verifyRecorder(callback) { + console.log(chalk.green(`Check to see if a record was created for the unit id: ${this.id} | name: ${this.assetProps.name}`)); + let startDate = parseInt(this.startTime) - 3000; + let stopDate = parseInt(+new Date() + 30 * 1000); + setTimeout(() => { this._getRecord(startDate, stopDate, this.configs); }, 3000); + } + + _getRecord(startDate, stopDate, configs) { + + + console.log(`${this.apiEndpoint}/asset-history/${this.id}/call-history/${startDate}/${stopDate}/10`); + + // console.log(this.apiEndpoint + `/history-pagination/1/300/${this.id}/${startDate}/${stopDate}/0/0/0/1/null`) + Request.get( + `${this.apiEndpoint}/asset-history/${this.id}/call-history/${startDate}/${stopDate}/10`, + // this.apiEndpoint + `/history-pagination/1/300/${this.id}/${startDate}/${stopDate}/0/0/0/1/null`, + { + headers: { + Authorization: `Bearer ${this.token}` + } + }, + (error, response, body) => { + if(!error && (response.statusCode === 200 || response.statusCode === 201)) { + let bodyObj = JSON.parse(body); + // console.log('bodyObj', bodyObj) + if(bodyObj.data.length > 0) { + + // Get the latest record + let latest = { id: 0 } + bodyObj.data.forEach(e => { + if(e.id > latest.id) { + latest = e; + } + }) + console.log(chalk.green(`[RECORDER] Record found(${latest.id}) for asset ${this.id} ✓`)); + utils.writeLog(`[RECORDER] Record found(${latest.id}) for asset ${this.id}`) + } else { + console.log(chalk.yellow(`[RECORDER] for asset ${this.id} not found`)); + } + } else { + utils.writeLog(`Error getting record for asset ${this.id} | ${error}`); + utils.writeErrorLog(`ERROR_RECORDER`); + } + let assetIds = configs.assets.ids; + + this._start(); + + } + ); + } + + _makePtt(callback) { + // Sending group monitoring before we make PTT() + // Is used for the HUB load test(_moveToChannel is sent anyway after connecting to murmur-register) + if (this.configs.settings.send_group_monitoring_before_each_call == 'true') { + this._moveToChannel(); + } + + // Check hub. + let hub = this.hub; + if (!hub || !hub.connected) { + return callback(); + } + + // Send ptt-press and wait for it to be accepted. + this._sendPttPress((isAccepted) => { + + if (!isAccepted) { + return callback(); + } + + // Select a random track from sounds.tracks + this.soundPath = `${currentPath}/sounds/${Math.floor(Math.random() * (this.configs.sounds.tracks.length) + 1)}.mp3` + + // Ptt accepted. We can send voice. + // Mp3 read stream. + var mp3 = fs.createReadStream(this.soundPath); + mp3.on('error', (e) => { + utils.writeErrorLog(`Error _makePtt asset ${this.id}`, JSON.stringify(e)); + }); + + mp3.on('data', (data) => { + + }); + + // Decoded mp3. + var lameDecoder = mp3 + .pipe(new lame.Decoder) + .on('data', (data) => { + + }) + + // On format we continue pipeing in. We need the mp3 format to know what to do next. Eg: To resample the mp3 we need to know the current sample rate. + .on('format', (format) => { + + // Create resampler. + var resampler = new Resampler({ + unsafe: false, + type: Resampler.Type.ZERO_ORDER_HOLD, + ratio: 48000 / format.sampleRate, + channels: format.channels + }) + + // Create mumble voice stream. + var voiceStream = this.mumble.client.createVoiceStream(0, format.channels); + + // Send ptt-release on voice end. + voiceStream.on('end', () => { + this.pttEndSuccessfully = true; + setTimeout(() => { + this._sendPttRelease(); + callback(); + }, 1500); // Hangtime + }); + + // @TODO: Ugly hack for the voice to work with mp3 (The fix is to remove float32ToInt16 conversion). It is the _transform function from OpusEncoderStream class (mumble-client-codecs-node project). + voiceStream._readableState.pipes._transform = function(chunk, encoding, callback) { + if (!this._opus) { + this._opus = new OpusEncoder(48000, chunk.numberOfChannels); + } + + callback(null, { + target: chunk.target, + codec: 'Opus', + frame: this._opus.encode(chunk.pcm), + position: chunk.position + }); + } + + // This is used to slow down the mp3 transmision. Murmur will drop packages that are too early. This will make the mp3 transmision real time, just like it is a microphone stream. + var throtler = new Transform({ + transform (data, _, callback) { + setTimeout(() => { + callback(null, data) + }, 16 / format.channels) + }, + readableObjectMode: true + }); + + // Used for converting buffer to float32. Needed for Murmur. + const buffer2Float32Array = new Transform({ + transform (data, _, callback) { + callback(null, new Float32Array(data.buffer, data.byteOffset, data.byteLength / 4)) + }, + readableObjectMode: true + }); + + // Pipe on. + lameDecoder + + // Make 4096 chunks to be like the microphone stream. + // .pipe(chunker(4096, {flush: true, align: true})) + + // Resample to 48000 hz. + .pipe(resampler) + + // Make 4 * 480 chunks for Murmur purposes. + .pipe(chunker(4 * 480, {flush: true, align: true})) + + // Slow down the stream. + .pipe(throtler) + + // Convert to float 32. + .pipe(buffer2Float32Array) + + // Used for testing in nodejs speaker. + // .pipe(new Speaker) + + // Mumble voice stream. + .pipe(voiceStream); + }); + }) + } + + _sendGPS(callback) { + let hub = this.hub; + if (hub && hub.connected) { + let lat = this.configs.settings.gps_lat_start_point; + let lng = this.configs.settings.gps_lng_start_point; + + setTimeout(() => { + var new_lat = this._randomCoordinates(lat); + var new_lng = this._randomCoordinates(lng); + + hub.emit('gps', JSON.stringify( + { + unix_time: 1467126677000, + asset_id: this.assetProps.id, + asset_sip_id: this.assetProps.sip_id, + speed_kmh: 16, + lat: new_lat, + lng: new_lng, + accuracy: 20.3, + activity_type: "driving", + activity_confidence: 90 + } + )); + lat = new_lat; + lng = new_lng; + if (!this.configs.settings.send_voice) { + this._start(); + } + }, this.configs.settings.gps_report_interval); + } + } + + _randomCoordinates(coordinate) { + if(Math.round(Math.random()) === 0) { + return parseFloat(parseFloat(coordinate) - parseFloat((Math.random() * (0.01 - 0.005) + 0.005).toFixed(4))).toFixed(6); + } else { + return parseFloat(parseFloat(coordinate) + parseFloat((Math.random() * (0.01 - 0.005) + 0.005).toFixed(4))).toFixed(6); + } + } + + _sendPttPress(callback) { + let hub = this.hub; + if (hub && hub.connected) { + + var pttAcceptHandler = (data) => { + data = JSON.parse(data); + if (data.asset_id != this.id) { + return; + } + utils.writeLog(`Asset ${this.id} sending PTT-PRESS to group ${this.groupId}`) + hub.removeListener('ptt-deny', pttDenyHandler); + callback(true); + }; + hub.once('ptt-accept', pttAcceptHandler); + + var pttDenyHandler = (data) => { + data = JSON.parse(data); + if (data.asset_id != this.id) { + return; + } + utils.writeLog(`Asset ${this.id} received PTT-DENY`); + hub.removeListener('ptt-accept', pttAcceptHandler); + callback(false); + }; + hub.once('ptt-deny', pttDenyHandler); + + hub.emit('ptt-press', JSON.stringify( + { + destination_group_id: this.groupId, + destination_group_sip_id: this.groupSipId, + destination_asset_id: 0, + destination_asset_sip_id: 0, + asset_id: this.id, + asset_sip_id: this.assetProps.sip_id, + asset_alias : this.assetProps.name, + priority : this.assetProps.priority, + portable_asset_id: 0, + portable_alias: null, + } + )); + } + } + + _sendPttRelease() { + let hub = this.hub; + if (hub && hub.connected) { + + utils.writeLog(`Asset ${this.id} sending PTT-RELEASE to group ${this.groupId}`); + hub.emit('ptt-release', JSON.stringify( + { + destination_group_id: this.groupId, + destination_group_sip_id: this.groupSipId, + destination_asset_id: 0, + destination_asset_sip_id: 0, + asset_id: this.id, + asset_sip_id: this.assetProps.sip_id, + asset_alias : this.assetProps.name, + priority : this.assetProps.priority, + portable_asset_id: 0, + portable_alias: null, + } + )); + } + } + +} + module.exports = Asset; \ No newline at end of file