-
Notifications
You must be signed in to change notification settings - Fork 63
/
airspy.lua
375 lines (315 loc) · 14.4 KB
/
airspy.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
---
-- Source a complex-valued signal from an Airspy. This source requires the
-- libairspy library. The Airspy R2 and Airspy Mini dongles are supported.
--
-- @category Sources
-- @block AirspySource
-- @tparam number frequency Tuning frequency in Hz
-- @tparam number rate Sample rate in Hz (3 MHz or 6 MHz for Airspy Mini,
-- 2.5 MHz or 10 MHz for Airspy R2)
-- @tparam[opt={}] table options Additional options, specifying:
-- * `gain_mode` (string, default "linearity", choice of "custom", "linearity", "sensitivity")
-- * `lna_gain` (int, default 5 dB, for custom gain mode, range 0 to 15 dB)
-- * `mixer_gain` (int, default 1 dB, for custom gain mode, range 0 to 15 dB)
-- * `vga_gain` (int, default 5 dB, for custom gain mode, range 0 to 15 dB)
-- * `lna_agc` (bool, default false, for custom gain mode)
-- * `mixer_agc` (bool, default false, for custom gain mode)
-- * `linearity_gain` (int, default 10, for linearity gain mode, range 0 to 21)
-- * `sensitivity_gain` (int, default 10, for sensitivity gain mode, range 0 to 21)
-- * `biastee_enable` (bool, default false)
--
-- @signature > out:ComplexFloat32
--
-- @usage
-- -- Source samples from 135 MHz sampled at 6 MHz
-- local src = radio.AirspySource(135e6, 6e6)
--
-- -- Source samples from 91.1 MHz sampled at 3 MHz, with custom gain settings
-- local src = radio.AirspySource(91.1e6, 3e6, {gain_mode = "custom", lna_gain = 4,
-- mixer_gain = 1, vga_gain = 6})
--
-- -- Source samples from 91.1 MHz sampled at 2.5 MHz, with linearity gain mode
-- local src = radio.AirspySource(91.1e6, 2.5e6, {gain_mode = "linearity", linearity_gain = 8})
--
-- -- Source samples from 91.1 MHz sampled at 2.5 MHz, with sensitivity gain mode
-- local src = radio.AirspySource(91.1e6, 2.5e6, {gain_mode = "sensitivity", sensitivity_gain = 8})
--
-- -- Source samples from 144.390 MHz sampled at 2.5 MHz, with bias tee enabled
-- local src = radio.AirspySource(144.390e6, 2.5e6, {biastee_enable = true})
local ffi = require('ffi')
local block = require('radio.core.block')
local platform = require('radio.core.platform')
local debug = require('radio.core.debug')
local types = require('radio.types')
local async = require('radio.core.async')
local pipe = require('radio.core.pipe')
local AirspySource = block.factory("AirspySource")
function AirspySource:instantiate(frequency, rate, options)
self.frequency = assert(frequency, "Missing argument #1 (frequency)")
self.rate = assert(rate, "Missing argument #2 (rate)")
self.options = options or {}
self.gain_mode = self.options.gain_mode or "linearity"
self.biastee_enable = self.options.biastee_enable or false
if self.gain_mode == "custom" then
self.lna_gain = self.options.lna_gain or 5
self.mixer_gain = self.options.mixer_gain or 1
self.vga_gain = self.options.vga_gain or 5
self.lna_agc = self.options.lna_agc or false
self.mixer_agc = self.options.mixer_agc or false
elseif self.gain_mode == "linearity" then
self.linearity_gain = self.options.linearity_gain or 10
elseif self.gain_mode == "sensitivity" then
self.sensitivity_gain = self.options.sensitivity_gain or 10
else
error(string.format("Unsupported gain mode \"%s\".", self.gain_mode))
end
self:add_type_signature({}, {block.Output("out", types.ComplexFloat32)})
end
function AirspySource:get_rate()
return self.rate
end
ffi.cdef[[
enum airspy_error { AIRSPY_SUCCESS = 0, };
enum airspy_board_id { AIRSPY_BOARD_ID_PROTO_AIRSPY = 0, };
enum airspy_sample_type { AIRSPY_SAMPLE_FLOAT32_IQ = 0, };
struct airspy_device;
typedef struct {
uint32_t major_version;
uint32_t minor_version;
uint32_t revision;
} airspy_lib_version_t;
typedef struct {
struct airspy_device* device;
void* ctx;
void* samples;
int sample_count;
uint64_t dropped_samples;
enum airspy_sample_type sample_type;
} airspy_transfer_t, airspy_transfer;
typedef int (*airspy_sample_block_cb_fn)(airspy_transfer* transfer);
const char* airspy_error_name(enum airspy_error errcode);
int airspy_open(struct airspy_device** device);
int airspy_close(struct airspy_device* device);
void airspy_lib_version(airspy_lib_version_t* lib_version);
int airspy_board_id_read(struct airspy_device* device, uint8_t* value);
const char* airspy_board_id_name(enum airspy_board_id board_id);
int airspy_version_string_read(struct airspy_device* device, char* version, uint8_t length);
int airspy_start_rx(struct airspy_device* device, airspy_sample_block_cb_fn callback, void* rx_ctx);
int airspy_stop_rx(struct airspy_device* device);
int airspy_get_samplerates(struct airspy_device* device, uint32_t* buffer, const uint32_t len);
int airspy_set_samplerate(struct airspy_device* device, uint32_t samplerate);
int airspy_set_sample_type(struct airspy_device* device, enum airspy_sample_type sample_type);
int airspy_set_freq(struct airspy_device* device, const uint32_t freq_hz);
int airspy_set_lna_gain(struct airspy_device* device, uint8_t value);
int airspy_set_mixer_gain(struct airspy_device* device, uint8_t value);
int airspy_set_vga_gain(struct airspy_device* device, uint8_t value);
int airspy_set_lna_agc(struct airspy_device* device, uint8_t value);
int airspy_set_mixer_agc(struct airspy_device* device, uint8_t value);
int airspy_set_linearity_gain(struct airspy_device* device, uint8_t value);
int airspy_set_sensitivity_gain(struct airspy_device* device, uint8_t value);
int airspy_set_rf_bias(struct airspy_device* device, uint8_t value);
int airspy_set_packing(struct airspy_device* device, uint8_t value);
]]
local libairspy_available, libairspy = pcall(ffi.load, "airspy")
function AirspySource:initialize()
-- Check library is available
if not libairspy_available then
error("AirspySource: libairspy not found. Is libairspy installed?")
end
end
function AirspySource:initialize_airspy()
self.dev = ffi.new("struct airspy_device *[1]")
local ret
-- Open device
ret = libairspy.airspy_open(self.dev)
if ret ~= 0 then
error("airspy_open(): " .. ffi.string(libairspy.airspy_error_name(ret)))
end
-- Dump version info
if debug.enabled then
-- Look up library version
local lib_version = ffi.new("airspy_lib_version_t")
libairspy.airspy_lib_version(lib_version)
-- Look up firmware version
local firmware_version = ffi.new("char[128]")
ret = libairspy.airspy_version_string_read(self.dev[0], firmware_version, 128)
if ret ~= 0 then
error("airspy_version_string_read(): " .. ffi.string(libairspy.airspy_error_name(ret)))
end
firmware_version = ffi.string(firmware_version)
-- Look up board ID
local board_id = ffi.new("uint8_t[1]")
ret = libairspy.airspy_board_id_read(self.dev[0], board_id)
if ret ~= 0 then
error("airspy_board_id_read(): " .. ffi.string(libairspy.airspy_error_name(ret)))
end
board_id = ffi.string(libairspy.airspy_board_id_name(board_id[0]))
debug.printf("[AirspySource] Library version: %u.%u.%u\n", lib_version.major_version, lib_version.minor_version, lib_version.revision)
debug.printf("[AirspySource] Firmware version: %s\n", firmware_version)
debug.printf("[AirspySource] Board ID: %s\n", board_id)
end
-- Set sample type
ret = libairspy.airspy_set_sample_type(self.dev[0], ffi.C.AIRSPY_SAMPLE_FLOAT32_IQ)
if ret ~= 0 then
error("airspy_set_sample_type(): " .. ffi.string(libairspy.airspy_error_name(ret)))
end
-- Set sample rate
ret = libairspy.airspy_set_samplerate(self.dev[0], self.rate)
if ret ~= 0 then
local ret_save = ret
io.stderr:write(string.format("[AirspySource] Error setting sample rate %u S/s.\n", self.rate))
local num_sample_rates, sample_rates
-- Look up number of sample rates
num_sample_rates = ffi.new("uint32_t[1]")
ret = libairspy.airspy_get_samplerates(self.dev[0], num_sample_rates, 0)
if ret ~= 0 then
goto set_samplerate_error
end
-- Look up sample rates
sample_rates = ffi.new("uint32_t[?]", num_sample_rates[0])
ret = libairspy.airspy_get_samplerates(self.dev[0], sample_rates, num_sample_rates[0])
if ret ~= 0 then
goto set_samplerate_error
end
-- Print supported sample rates
io.stderr:write("[AirspySource] Supported sample rates:\n")
for i=0, num_sample_rates[0]-1 do
io.stderr:write(string.format("[AirspySource] %u\n", sample_rates[i]))
end
::set_samplerate_error::
error("airspy_set_samplerate(): " .. ffi.string(libairspy.airspy_error_name(ret_save)))
end
debug.printf("[AirspySource] Frequency: %u Hz, Sample rate: %u Hz\n", self.frequency, self.rate)
-- Disable packing
ret = libairspy.airspy_set_packing(self.dev[0], 0)
if ret ~= 0 then
error("airspy_set_packing(): " .. ffi.string(libairspy.airspy_error_name(ret)))
end
-- Set bias tee
ret = libairspy.airspy_set_rf_bias(self.dev[0], self.biastee_enable)
if ret ~= 0 then
error("airspy_set_rf_bias(): " .. ffi.string(libairspy.airspy_error_name(ret)))
end
if self.gain_mode == "custom" then
-- LNA gain
if not self.lna_agc then
-- Disable LNA AGC
ret = libairspy.airspy_set_lna_agc(self.dev[0], 0)
if ret ~= 0 then
error("airspy_set_lna_agc(): " .. ffi.string(libairspy.airspy_error_name(ret)))
end
-- Set LNA gain
ret = libairspy.airspy_set_lna_gain(self.dev[0], self.lna_gain)
if ret ~= 0 then
error("airspy_set_lna_gain(): " .. ffi.string(libairspy.airspy_error_name(ret)))
end
else
-- Enable LNA AGC
ret = libairspy.airspy_set_lna_agc(self.dev[0], 1)
if ret ~= 0 then
error("airspy_set_lna_agc(): " .. ffi.string(libairspy.airspy_error_name(ret)))
end
end
-- Mixer gain
if not self.mixer_agc then
-- Disable mixer AGC
ret = libairspy.airspy_set_mixer_agc(self.dev[0], 0)
if ret ~= 0 then
error("airspy_set_mixer_agc(): " .. ffi.string(libairspy.airspy_error_name(ret)))
end
-- Set mixer gain
ret = libairspy.airspy_set_mixer_gain(self.dev[0], self.mixer_gain)
if ret ~= 0 then
error("airspy_set_mixer_gain(): " .. ffi.string(libairspy.airspy_error_name(ret)))
end
else
-- Enable mixer AGC
ret = libairspy.airspy_set_mixer_agc(self.dev[0], 1)
if ret ~= 0 then
error("airspy_set_mixer_agc(): " .. ffi.string(libairspy.airspy_error_name(ret)))
end
end
-- Set VGA gain
ret = libairspy.airspy_set_vga_gain(self.dev[0], self.vga_gain)
if ret ~= 0 then
error("airspy_set_vga_gain(): " .. ffi.string(libairspy.airspy_error_name(ret)))
end
elseif self.gain_mode == "linearity" then
-- Set linearity gain
ret = libairspy.airspy_set_linearity_gain(self.dev[0], self.linearity_gain)
if ret ~= 0 then
error("airspy_set_linearity_gain(): " .. ffi.string(libairspy.airspy_error_name(ret)))
end
elseif self.gain_mode == "sensitivity" then
-- Set sensitivity gain
ret = libairspy.airspy_set_sensitivity_gain(self.dev[0], self.sensitivity_gain)
if ret ~= 0 then
error("airspy_set_sensitivity_gain(): " .. ffi.string(libairspy.airspy_error_name(ret)))
end
end
-- Set frequency
ret = libairspy.airspy_set_freq(self.dev[0], self.frequency)
if ret ~= 0 then
error("airspy_set_freq(): " .. ffi.string(libairspy.airspy_error_name(ret)))
end
end
local function read_callback_factory(...)
local ffi = require('ffi')
local radio = require('radio')
local pipe = require('radio.core.pipe')
local vector = require('radio.core.vector')
-- Convert fds on stack to Pipe objects
local output_pipes = {}
for i, fd in ipairs({...}) do
output_pipes[i] = pipe.Pipe()
output_pipes[i]:initialize(radio.types.ComplexFloat32, nil, fd)
end
-- Create pipe mux for write multiplexing
local pipe_mux = pipe.PipeMux({}, {output_pipes})
local function read_callback(transfer)
-- Check for dropped samples
if transfer.dropped_samples ~= 0 then
io.stderr:write(string.format("[AirspySource] Warning: %u samples dropped.\n", tonumber(transfer.dropped_samples)))
end
-- Calculate size of samples in bytes
local size = transfer.sample_count*ffi.sizeof("float")*2
-- Write to output pipes
local eof, eof_pipe, shutdown = pipe_mux:write({vector.Vector.cast(radio.types.ComplexFloat32, transfer.samples, size)})
if not shutdown and eof then
io.stderr:write("[AirspySource] Downstream block terminated unexpectedly.\n")
end
return 0
end
return ffi.cast('int (*)(airspy_transfer *)', read_callback)
end
function AirspySource:run()
-- Initialize the airspy in our own running process
self:initialize_airspy()
-- Create pipe mux for control socket
local pipe_mux = pipe.PipeMux({}, {}, self.control_socket)
-- Start receiving
local read_callback, read_callback_state = async.callback(read_callback_factory, unpack(self.outputs[1]:filenos()))
local ret = libairspy.airspy_start_rx(self.dev[0], read_callback, nil)
if ret ~= 0 then
error("airspy_start_rx(): " .. ffi.string(libairspy.airspy_error_name(ret)))
end
-- Wait for shutdown from control socket
while true do
-- Read control socket
local _, _, shutdown = pipe_mux:read()
if shutdown then
break
end
end
-- Stop receiving
ret = libairspy.airspy_stop_rx(self.dev[0])
if ret ~= 0 then
error("airspy_stop_rx(): " .. ffi.string(libairspy.airspy_error_name(ret)))
end
-- Close airspy
ret = libairspy.airspy_close(self.dev[0])
if ret ~= 0 then
error("airspy_close(): " .. ffi.string(libairspy.airspy_error_name(ret)))
end
end
return AirspySource