Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Working toward a production acceptance test for the HackRF, using a s…
…econd one connected via an attenuator
  • Loading branch information
cjheath committed Dec 11, 2024
commit d66d9ebd542159b8c1766aba5a8b800f06d12661
71 changes: 71 additions & 0 deletions examples/acceptance_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Configure a HackRF as test rig, another as test subject (DUT) and run tests interactively
*/

import { DeviceInfo, listDevices, UsbBoardId, visible_hackrfs, scan_hackrfs } from '../lib'
import { run_test_sequence } from './test_sequence'

function timeout(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}

async function await_hackrf(device: string) {
process.stdout.write(`Please connect ${device}: `);
for (;;) {
for await (const device_change of scan_hackrfs()) {
if (device_change.added) {
console.log(`\nPlugged in: ${device_change.added.serialNumber}`)
process.stdout.write("\n");
return device_change.added;
}
/* Ignore removals for now
else if (device_change.removed) {
console.log(`Unplugged: ${device_change.removed.serialNumber}`)
return undefined;
}
*/
}

await timeout(1000); // Sleep for a second before trying again
}
}

async function await_rig()
{
// Do the initial scan to see what's connected
for await (const device_change of scan_hackrfs()) { scan_hackrfs(); }

while (visible_hackrfs.length > 1) {
process.stdout.write(`${visible_hackrfs.length} HackRFs connected, unplug all but the test controller please\r`)
await timeout(1000); // Sleep for a second before trying again
for await (const device_change of scan_hackrfs()) { scan_hackrfs(); }
}

if (visible_hackrfs.length == 1)
{
console.log(`Detected ${visible_hackrfs[0].serialNumber} as test controller`)
return visible_hackrfs[0];
}
return await_hackrf("test controller HackRF");
}

async function await_dut()
{
return await_hackrf("HackRF to be tested");
}

async function main() {

var rig = await await_rig();
for (;;) {
var dut = await await_dut();
if (!dut) continue;

console.log(`Detected ${dut.serialNumber} as DUT`)

await run_test_sequence(rig, dut)
break;
}
process.exit(0)
}
main()
8 changes: 5 additions & 3 deletions examples/fm_receiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ async function main() {
const carrierFrequency = 101.7e6
const carrierDeviation = 75e3

const device = await open()
const serial = process.argv[2]
const device = await open(serial)
await device.setFrequency(carrierFrequency + tuneOffset)
await device.setSampleRate(fs)
await device.setAmpEnable(false)
await device.setLnaGain(24)
await device.setVgaGain(8)
await device.setLnaGain(32)
await device.setVgaGain(22)

// Collect audio samples & play back
const speaker = new Speaker({ sampleRate: 48000, channels: 1, bitDepth: 16 })
Expand Down Expand Up @@ -61,6 +62,7 @@ async function main() {
})
speaker.destroy()
console.error('\nDone, exiting')
process.exit(0)
}
main()

Expand Down
12 changes: 8 additions & 4 deletions examples/fm_tone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,28 @@ async function main() {
const toneFrequency = 400
const toneAmplitude = .4

const device = await open()
const serial = process.argv[2]
const device = await open(serial)
await device.setFrequency(carrierFrequency)
await device.setSampleRate(fs)
await device.setAmpEnable(false)
await device.setTxVgaGain(30)
await device.setTxVgaGain(47)

const tone = makeToneGeneratorF(fs, toneFrequency, toneAmplitude)
const modulator = makeFrequencyMod(fs, carrierDeviation, carrierAmplitude)
const signal = () => quantize( modulator(tone()) )

console.log(`Transmitting at ${(carrierFrequency/1e6).toFixed(2)}MHz...`)
process.on('SIGINT', () => device.requestStop())
process.on('SIGINT', () => { device.requestStop(); })
let block_count = 0;
await device.transmit(array => {
block_count++;
const samples = array.length / 2
for (let n = 0; n < samples; n++)
array.set(signal(), n * 2)
})
console.log('\nDone, exiting')
console.log(`\nDone after ${block_count} blocks, exiting`);
process.exit(0)
}
main()

Expand Down
15 changes: 15 additions & 0 deletions examples/list_devices.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

import { listDevices, UsbBoardId } from '../lib'

async function main() {
let ld = listDevices();
console.log(ld)
for await (const info of ld) {
console.log(info.device)
console.log(`Found ${info.usbBoardId} = ${UsbBoardId[info.usbBoardId]}`)
console.log(`Serial: ${info.serialNumber}`)
}

process.exit(0)
}
main()
25 changes: 25 additions & 0 deletions examples/scan_hackrfs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Continuously scan for HackRF devices, announcing when each appears or disappears
*/

import { DeviceInfo, listDevices, UsbBoardId, visible_hackrfs, scan_hackrfs } from '../lib'

function timeout(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}

async function main() {
for (;;) {
for await (const device_change of scan_hackrfs()) {
if (device_change.added) {
console.log(`Plugged in: ${device_change.added.serialNumber}`)
} else if (device_change.removed) {
console.log(`Unplugged: ${device_change.removed.serialNumber}`)
}
}

await timeout(1000); // Sleep for a second before trying again
}
process.exit(0)
}
main()
90 changes: 90 additions & 0 deletions examples/test_sequence.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Testing sequence for the HackRF.
*
* Initially just test the noise floor at a range of frequencies.
* Noise floor is simplified to just the variance in the magnitude of the I/Q vectors.
*/
import { open, HackrfDevice, DeviceInfo, UsbBoardId } from '../lib'

type Complex = [number, number]

function timeout(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}

/*
* Not really an FFT, not yet. But aspiring to be
*/
class FFT {
protected num_samples: number = 0;
protected sum_mag: number = 0;
protected sum_mag_squared: number = 0;

constructor(_logSize: number) {
// Allocate array of size 2^_logSize
}

accumulate(x: Complex): void {
const mag_squared = x[0]*x[0] + x[1]*x[1]
this.sum_mag_squared += mag_squared;
this.num_samples++;
}

noise_floor(): number
{
return Math.sqrt(this.sum_mag_squared/this.num_samples);
}
}

async function noise_floor_test(dut: HackrfDevice, frequency: number, sample_rate: number): Promise<number | string> {
console.log(`Measuring noise floor at ${frequency}`)

const num_seconds = 1;

await dut.setFrequency(frequency)
await dut.setSampleRate(sample_rate)
await dut.setAmpEnable(false)
await dut.setLnaGain(32)
await dut.setVgaGain(48)

var fft = new FFT(12);
var num_samples = 0;
await dut.receive((array): undefined | void | false => {
if (num_samples >= num_seconds*sample_rate)
return; // Discard overrun
const samples = array.length / 2
for (let n = 0; n < samples; n++)
{
if (++num_samples == num_seconds*sample_rate) { // Collect 1 second of data
dut.requestStop();
break;
}
const i = array[n * 2 + 0] / 127
const q = array[n * 2 + 1] / 127
fft.accumulate([i, q])
}
})
return fft.noise_floor();
}

export async function run_test_sequence(rig: DeviceInfo, dut: DeviceInfo)
{
const device: HackrfDevice = await open(dut.serialNumber)
if (!device)
return `device ${dut.serialNumber} not available`;

console.log(`Testing ${dut.serialNumber} using ${rig.serialNumber}:\n`);

const frequencies = [80e6, 600e6, 2.4e9, 3.6e9]
var i: number;
for (i = 0; i < frequencies.length; i++) {
const frequency = frequencies[i];
var noise_floor = await noise_floor_test(device, frequency, 1.2e6);
if (typeof(noise_floor) == 'string')
{
console.log("Failed to open DUT for testing");
return;
}
console.log(`Noise floor at ${frequency/1e6} is ${Math.round(noise_floor*1280)/10}`)
}
}
3 changes: 2 additions & 1 deletion examples/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"strictNullChecks": true,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
"emitDecoratorMetadata": true,
"outDir": "../dist"
}
}
4 changes: 4 additions & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ export {
DeviceInfo, HackrfDevice, listDevices, open,
StreamOptions, defaultStreamOptions,
} from './interface'

export {
visible_hackrfs, scan_hackrfs
} from './scan'
28 changes: 28 additions & 0 deletions lib/scan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Continuously scan for HackRF devices, announcing when each appears or disappears
*/

import { DeviceInfo, listDevices, UsbBoardId } from '.'

/*
* This array contains all the HackRF devices that were visible on the last scan_hackrfs
*/
export var visible_hackrfs: Array<DeviceInfo>;
visible_hackrfs = new Array<DeviceInfo>;

export async function* scan_hackrfs() {
var disappeared_hackrfs = [...visible_hackrfs]; // Copy devices that were visible previously
for await (const extant of listDevices()) {
var found = visible_hackrfs.find(e => e.usbBoardId == extant.usbBoardId && e.serialNumber == extant.serialNumber)
if (found) { // Still present, it's not disappeared
disappeared_hackrfs.splice(disappeared_hackrfs.indexOf(found), 1);
} else { // New device
visible_hackrfs.push(extant);
yield({ added: extant }); // New device found
}
}
for (const gone of disappeared_hackrfs) {
yield({ removed: gone }); // Device unplugged
visible_hackrfs.splice(visible_hackrfs.indexOf(gone), 1);
}
}
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"declaration": true,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
"emitDecoratorMetadata": true,
},
"include": [
"lib",
Expand Down