2019-09-18 08:11:16 +00:00
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 ) {
this . id = id ;
this . configs = configs
this . _processConfigs ( ) ;
this . startTime = + new Date ( ) ;
this . pttEndSuccessfully = false ;
2019-09-25 11:02:03 +00:00
this . assetProps = { } ;
utils . writeLog ( ` Creating asset ${ id } ` )
2019-09-18 08:11:16 +00:00
// 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 ) => {
2019-09-25 11:02:03 +00:00
utils . writeLog ( ` Asset ${ this . id } was successfully initialized ` )
2019-10-02 07:38:26 +00:00
2019-09-18 08:11:16 +00:00
if ( err ) {
2019-10-09 08:26:26 +00:00
utils . writeLog ( ` Asset ${ this . id } was not successfully initialized ` , err ) ;
2019-09-18 08:11:16 +00:00
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 ) : '' ) ;
this . soundPath = currentPath
// this.soundPath += '/sounds/sound.mp3';
this . soundPath += '/sounds/' + this . configs . sounds [ 'sound' ] ;
// if (this.configs.sounds['custom' + this.id]) {
// // Custom path sound.
// this.soundPath += '/sounds/' + this.configs.sounds['custom' + this.id];
// } else {
// // Normal path sound.
// this.soundPath += '/sounds/sample' + (this.id % this.configs.sounds.sounds_total_number) + '.mp3';
// }
}
_getDataFromApi ( callback ) {
Request . get (
this . apiEndpoint + '/asset/' + this . id ,
{ } ,
( error , response , body ) => {
2019-09-25 11:02:03 +00:00
utils . writeLog ( ` Get informations about asset ${ this . id } ` )
2019-09-18 08:11:16 +00:00
if ( ! error && ( response . statusCode === 200 || response . statusCode === 201 ) ) {
let bodyObj = JSON . parse ( body ) ;
// Here are the asset fields.
this . assetProps = bodyObj . data ;
return this . _getGroupsFromApi ( callback ) ;
} else {
2019-10-09 08:26:26 +00:00
utils . writeLog ( ` ERROR getting informations about asset ${ this . id } ` , error ) ;
utils . writeErrorLog ( ` ERROR_API ` ) ;
2019-09-18 08:11:16 +00:00
return callback ( error ) ;
}
}
) ;
}
_getGroupsFromApi ( callback ) {
Request . get (
this . apiEndpoint + '/asset/ ' + this . id + '/groups' ,
{ } ,
( 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 ) ;
}
2019-09-25 11:02:03 +00:00
utils . writeLog ( ` Informations about asset ${ this . id } received ` )
2019-09-18 08:11:16 +00:00
return callback ( ) ;
} else {
2019-10-09 08:26:26 +00:00
utils . writeLog ( ` Error getting informations about asset ${ this . id } ` , error ) ;
utils . writeErrorLog ( ` ERROR_API ` ) ;
2019-09-18 08:11:16 +00:00
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' , ( ) => {
2019-10-09 08:26:26 +00:00
utils . writeLog ( ` Asset ${ this . id } disconnected from HUB ` ) ;
2019-09-18 08:11:16 +00:00
} ) ;
2019-10-09 08:26:26 +00:00
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 ` ) ;
} ) ;
2019-09-18 08:11:16 +00:00
hub . once ( 'connect' , ( ) => {
return callback ( ) ;
} ) ;
hub . on ( 'connect' , ( ) => {
2019-10-09 08:26:26 +00:00
utils . writeLog ( ` Asset ${ this . id } connected to HUB ` ) ;
2019-09-18 08:11:16 +00:00
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 ) {
2019-09-25 11:02:03 +00:00
utils . writeLog ( ` Asset ${ this . id } sending ARS ` )
2019-09-18 08:11:16 +00:00
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 . mumble = new Mumble ( this . id , this . configs , ( err ) => {
if ( err ) {
2019-10-09 08:26:26 +00:00
utils . writeLog ( ` Asset ${ this . id } Murmur connection error ` , err ) ;
utils . writeErrorLog ( ` ERROR_MURMUR ` ) ;
2019-10-02 07:38:26 +00:00
return callback ( err ) ;
} else {
2019-09-18 08:11:16 +00:00
return callback ( ) ;
2019-10-02 07:38:26 +00:00
// setTimeout(()=> {
// return callback();
// }, 1000);
}
2019-09-18 08:11:16 +00:00
} ) ;
}
_register ( callback ) {
Request . post (
this . apiEndpoint + '/audio/register/' + this . id ,
{ } ,
( error , response , body ) => {
if ( ! error && ( response . statusCode === 200 || response . statusCode === 201 ) ) {
2019-09-25 11:02:03 +00:00
utils . writeLog ( ` Asset ${ this . id } audio registered ` ) ;
2019-09-18 08:11:16 +00:00
return callback ( ) ;
} else {
2019-10-09 08:26:26 +00:00
utils . writeLog ( ` Asset ${ this . id } audio registered error ` , error ) ;
utils . writeErrorLog ( ` ERROR_API ` ) ;
2019-09-18 08:11:16 +00:00
return callback ( error ) ;
}
}
) ;
}
_moveToChannel ( callback ) {
Request . post (
this . apiEndpoint + '/audio/enter-group/' + this . id + '/' + this . groupId ,
{ } ,
( 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
}
) ) ;
2019-09-25 11:02:03 +00:00
utils . writeLog ( ` Asset ${ this . id } mmonitoring group ${ this . groupId } ` )
2019-09-18 08:11:16 +00:00
return callback ( ) ;
} else {
return callback ( 'Cannot send group-monitoring: Hub not connected' ) ;
}
} else {
2019-10-09 08:26:26 +00:00
utils . writeLog ( ` Asset ${ this . id } audio enter group error ` , error ) ;
utils . writeErrorLog ( ` ERROR_API ` ) ;
2019-09-18 08:11:16 +00:00
return callback ( error ) ;
}
}
) ;
}
_start ( ) {
2019-09-25 11:02:03 +00:00
if ( this . configs . settings . send _voice ) {
2019-09-18 08:11:16 +00:00
this . _makePtt ( ( ) => {
if ( this . pttEndSuccessfully ) {
this . _verifyRecorder ( ) ;
}
} ) ;
}
2019-09-25 11:02:03 +00:00
if ( this . configs . settings . send _gps ) {
this . _sendGPS ( ) ;
}
2019-09-18 08:11:16 +00:00
}
_verifyRecorder ( callback ) {
console . log ( chalk . green ( ` Check to see if a record was created for the unit id: ${ this . id } | name: ${ this . assetProps . name } ` ) ) ;
2019-10-24 13:36:42 +00:00
let startDate = parseInt ( this . startTime ) - 5000 ;
2019-09-18 08:11:16 +00:00
let stopDate = parseInt ( + new Date ( ) + 60 * ( 1000000 * 60 ) ) ;
2019-10-24 13:36:42 +00:00
setTimeout ( ( ) => { this . _getRecord ( startDate , stopDate ) ; } , 3000 ) ;
2019-09-18 08:11:16 +00:00
}
_getRecord ( startDate , stopDate ) {
console . log ( this . apiEndpoint + ` /history-pagination/1/300/ ${ this . id } / ${ startDate } / ${ stopDate } /0/0/0/1 ` )
Request . get (
this . apiEndpoint + ` /history-pagination/1/300/ ${ this . id } / ${ startDate } / ${ stopDate } /0/0/0/1 ` ,
{ } ,
( 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 ;
}
} )
2019-09-25 11:02:03 +00:00
console . log ( chalk . green ( ` [RECORDER] Record found( ${ latest . id } ) for asset ${ this . id } | description: ${ latest . description } ✓ ` ) ) ;
utils . writeLog ( ` [RECORDER] Record found( ${ latest . id } ) for asset ${ this . id } | description: ${ latest . description } ` )
2019-09-18 08:11:16 +00:00
} else {
2019-09-25 11:02:03 +00:00
console . log ( chalk . yellow ( ` [RECORDER] for asset ${ this . id } not found ` ) ) ;
2019-09-18 08:11:16 +00:00
}
} else {
2019-10-09 08:26:26 +00:00
utils . writeLog ( ` Error getting record for asset ${ this . id } ` ) ;
utils . writeErrorLog ( ` ERROR_RECORDER ` ) ;
2019-09-25 11:02:03 +00:00
throw error ;
}
// DEBUG
// console.log('this.id', this.id)
// console.log('Math.max(...this.configs.assets.ids', Math.max(...this.configs.assets.ids))
// console.log('this.id === Math.max(...this.configs.assets.ids', this.id === Math.max(...this.configs.assets.ids))
if ( this . id === Math . max ( ... this . configs . assets . ids ) ) {
setTimeout ( ( ) => {
utils . writeLog ( 'STOP' )
. then ( ( ) => {
process . exit ( 0 ) ;
} ) ;
2019-10-24 13:36:42 +00:00
} , 15000 ) ;
2019-09-18 08:11:16 +00:00
}
2019-09-25 11:02:03 +00:00
2019-09-18 08:11:16 +00:00
}
) ;
}
_makePtt ( callback ) {
// Send ptt-press, transmit all the sound then send ptt-release.
// 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 ( ) ;
}
// Ptt accepted. We can send voice.
// Mp3 read stream.
var mp3 = fs . createReadStream ( this . soundPath ) ;
mp3 . on ( 'error' , ( e ) => {
2019-10-07 13:14:37 +00:00
utils . writeErrorLog ( ` Error _makePtt asset ${ this . id } ` , e ) ;
2019-09-18 08:11:16 +00:00
} ) ;
2019-10-09 08:26:26 +00:00
2019-09-18 08:11:16 +00:00
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 ) ;
} ) ;
} )
}
2019-09-25 11:02:03 +00:00
_sendGPS ( callback ) {
let hub = this . hub ;
if ( hub && hub . connected ) {
2019-09-25 14:31:02 +00:00
let lat = this . configs . settings . gps _lat _start _point ;
let lng = this . configs . settings . gps _lng _start _point ;
2019-09-25 11:02:03 +00:00
setInterval ( ( ) => {
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 ) ;
}
}
2019-09-18 08:11:16 +00:00
_sendPttPress ( callback ) {
let hub = this . hub ;
if ( hub && hub . connected ) {
var pttAcceptHandler = ( data ) => {
data = JSON . parse ( data ) ;
if ( data . asset _id != this . id ) {
return ;
}
2019-09-25 11:02:03 +00:00
utils . writeLog ( ` Asset ${ this . id } sending PTT-PRESS to group ${ this . groupId } ` )
2019-09-18 08:11:16 +00:00
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 ;
}
2019-09-25 11:02:03 +00:00
utils . writeLog ( ` Asset ${ this . id } received PTT-DENY ` ) ;
2019-09-18 08:11:16 +00:00
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 ) {
2019-09-25 11:02:03 +00:00
utils . writeLog ( ` Asset ${ this . id } sending PTT-RELEASE to group ${ this . groupId } ` ) ;
2019-09-18 08:11:16 +00:00
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 ;