PowerChangeDetector

Warning

This feature is available only with a License - Contact us for more details

Changes detector based on energy level analysis.

  • A first snapshot of about 300 milliseconds is taken and the average power is calculated for nbins frequencies.
  • Then, each call to runDetections () performs a new estimation and the power per frequency is compared to the reference value.
  • A detection is returned ( popDetection() method) if the power level has increased by at least the threshold value (by default 4.0 for BladeRF interfaces or 10.0 for Soapy interfaces) compared to the reference.

There are two different ways to feed the ChangeDetector object :

  • Automatic : use the setRX( JSReceiver rx ) method to plug the output of the receiver to feed the detector,
  • Manual : use the pushIQ( IQData block) method to add IQSamples in the processing queue.

The first call to either setRX( JSReceiver rx ) or pushIQ( IQData block) sets the working mode and cannot be changed.

object PowerChangeDetector {
    constructor PowerChangeDetector( number fcenterMHz, int nbins );

    bool setRx( JSReceiver rx );
     or
    bool pushIQ( IQData data );
    bool updateReference();

    int runDetections( [optional bool updateReference ]);
    void clearDetections();

    bool ignore( number freq_start [MHz], freq_stop [MHz] );
    object popDetection() ;
    IQData getLastCapture() ; // returns the IQ data that were used for the detection
    number getPowerThreshold(); // returns the current power threshold value
    void setPowerThreshold( number ) ; // change the power threshold value


    void freezeFor( number activityID, number howlong_milliseconds );
}

.setRx

Indicate which receiver ( a JSRadio object) type should be used to carry out the acquisition of samples.

var s = new PowerChangeDetector(466, 256);
s.setRx(rx);

.pushIQ

.updateReference

The power detector is based on a comparison with a reference value. The reference is configured to require at least 300 milliseconds of signal.

  • When using the automatic mode (via setRX( JSReceiver rx )) , the receiver is driven to capture the required number of samples,
  • When using the manual mode (via pushIQ( IQData block)) the reference will be estimated/updated only if enough samples are available.

The boolean value returned by the function updateReference() tells if a reference has been generated or not.

.ignore

.ignore( number freq_start [MHz], freq_stop [MHz] );

Ignores a frequency range : no change will be reported from freq_start to freq_stop, whatever happens.

.runDetections

Perform a cycle of detections and returns the number of signals detected which can be read by popDetections ().
By default the reference of power used for the detections is updated (sliding average).
To not update the reference level, use:

.runDetections( false );

Example:

  • Configure the object,
  • Create an "atmosphere" on a few iterations,
  • then operate the detector
// Configure the change detector
var s = new PowerChangeDetector(centerFreq, bands);
s.setRx(rx);
s.setPowerThreshold( threshold ) ; // dB threshold
s.ignore(centerFreq - 0.01, centerFreq + 0.01); // Ignore center of rx (dc bias)
// run a fiew iterations to improve reference
for (var i = 0; i < 30; i++) {
    s.updateReference();
}
print('Reference done, run detector.');

for (; ;) {
    var changes = s.runDetections(false);

    if (changes == 0) {
        //print('no change, continue');
        continue;
    }

   ...
}

Clear all pending detections (not yet read by popDetection)

.popDetection

Used to retrieve a detection identified during the call to runDetections (). The returned object has the following properties :

object :   {
    data_type: 'activity',
    id: number, /* sequential */
    center : qrg MHz,
    bw  : Hz,
    power_level : float,
    snr : float,
    seen_start : timestamp millisecond,
    seen_stop : 0 or timestamp millisecond,
    status_code: 1 or 2 or 3, /* see below */
    status_msg: STARTED, UPDATE, ENDED
 }

.getLastCapture

Returns the IQData object containing the raw data used for the detection. See IQ Object for details.

.getPowerThreshold

Returns the current threshold value

number getPowerThreshold();

.setPowerThreshold

Set the current value of threshold level. Detections will be triggered when the power level in the FFT bin is:
level >= reference_level + power_threshold

.setPowerThreshold( number ) ;

Example with RX :

var rx = Soapy.makeDevice({'query' : 'driver=rtlsdr' }) ;
if( typeof rx != 'object' ) {
    print('no radio ?');
    exit();
}

if( !rx.isValid()) {
    print('no radio ?');
    exit();
}

if( rx.setRxSampleRate( 2e6 )) {
      print('Sample rate changed');
}

var s = new PowerChangeDetector( 466, 256 ) ;
s.setRx( rx );
// Generate a background reference over several captures
for( i=0 ; i < 10 ; i++ ) {
 s.updateReference();
 sleep(500);
}

// now run
for( i=0 ; i < 10 ; i++ ) {
    print('------------' + i );

    var changes = s.runDetections();
    //print( changes + ' detected changes');
    if( changes > 0 ) {             
           // investigate detections
            var detection = s.popDetection() ;
            while( typeof detection === 'object' && detection !== null ) {      
                print(JSON.stringify(detection));
                detection = s.popDetection() ;
            }
    } 
}
print('finished');

Example with data from a queue :

var IQBlock = new IQData('iq');
var fifo_from_rx = Queues.create( 'input');
// open RX 
var rx = Soapy.makeDevice( {'query' : 'driver=rtlsdr' });
if( !rx.isValid()) {
    print('no radio ?');
    exit();
}

if( rx.setRxSampleRate( 2e6 )) {
      print('Sample rate changed');
}

rx.setRxCenterFreq( 466 );

// engage streaming
if( !fifo_from_rx.ReadFromRx( rx ) ) {
    print('Cannot stream from rx');
    exit();
}

// now loop : read IQ block from radio, do something
// 
var state = 0 ;
var references = 0 ;
var s = new PowerChangeDetector( 466, 256 ) ;

while( fifo_from_rx.isFromRx() ) {
    if( IQBlock.readFromQueue( fifo_from_rx ) ) {    // load samples from input queue into IQBlock object
        if( state != 1 )
            print('Pushing ' + IQBlock.getLength() + ' samples.');

        // push data to detector
        s.pushIQ( IQBlock );

        // in the begining, we ask to make a reference, then we detect
        switch( state ) {
        case 0 :
        if( s.updateReference() ) { // here we need to check the return... the reference process takes time and may need more samples than we have already pushed
            references++ ;
            print('Reference updated.');
        }

        if( references > 10 ) {
            print('Reference set, now run detector');
            state = 1 ;
        }
        break ;

    case 1:
        var changes = s.runDetections();
        if( changes > 0 ) {             
               // investigate detections
            var detection = s.popDetection() ;
            while( typeof detection === 'object' && detection !== null ) {      
                print(JSON.stringify(detection));
                detection = s.popDetection() ;
            }
        } 
        break ;
        }
    }
}

Last update: December 21, 2021