bash
复制代码
classdef KrakenSDRClient < handle
properties
ip_addr = '127.0.0.1'
data_port = 5000
ctrl_port = 5001
num_channels = 5
freq_hz
gain
debug = false
timeout = 5.0
valid_gains = [ ...
0, 0.9, 1.4, 2.7, 3.7, 7.7, 8.7, 12.5, 14.4, 15.7, ...
16.6, 19.7, 20.7, 22.9, 25.4, 28.0, 29.7, 32.8, 33.8, ...
36.4, 37.2, 38.6, 40.2, 42.1, 43.4, 43.9, 44.5, 48.0, 49.6];
connected = false
data_client
ctrl_client
iq_header
end
properties (Constant)
FRAME_TYPE_DATA = 0
FRAME_TYPE_DUMMY = 1
FRAME_TYPE_RAMP = 2
FRAME_TYPE_CAL = 3
FRAME_TYPE_TRIGW = 4
SYNC_WORD = uint32(hex2dec('2BF7B95A'))
HEADER_SIZE = 1024
RESERVED_BYTES = 192
end
methods
function obj = KrakenSDRClient(ip_addr, data_port, ctrl_port, num_channels, freq_mhz, gain, debug_flag, timeout)
if nargin >= 1 && ~isempty(ip_addr), obj.ip_addr = ip_addr; end
if nargin >= 2 && ~isempty(data_port), obj.data_port = data_port; end
if nargin >= 3 && ~isempty(ctrl_port), obj.ctrl_port = ctrl_port; end
if nargin >= 4 && ~isempty(num_channels), obj.num_channels = num_channels; end
if nargin >= 5 && ~isempty(freq_mhz)
obj.freq_hz = uint64(freq_mhz * 1e6);
else
obj.freq_hz = uint64(416.588 * 1e6);
end
if nargin >= 6 && ~isempty(gain)
obj.gain = obj.normalize_gain(gain);
else
obj.gain = obj.normalize_gain(10.0);
end
if nargin >= 7 && ~isempty(debug_flag), obj.debug = logical(debug_flag); end
if nargin >= 8 && ~isempty(timeout), obj.timeout = timeout; end
obj.iq_header = struct();
end
function gain_list = normalize_gain(obj, gain_in)
if isempty(gain_in)
gain_list = 10.0 * ones(1, obj.num_channels);
elseif isnumeric(gain_in) && isscalar(gain_in)
gain_list = double(gain_in) * ones(1, obj.num_channels);
elseif isnumeric(gain_in) && isvector(gain_in)
if numel(gain_in) ~= obj.num_channels
error('gain list length must be %d', obj.num_channels);
end
gain_list = double(gain_in(:)).';
else
error('gain must be a scalar or a numeric vector');
end
end
function connect(obj)
if obj.connected
return;
end
obj.data_client = tcpclient(obj.ip_addr, obj.data_port, 'Timeout', obj.timeout);
write(obj.data_client, uint8('streaming'), 'uint8');
obj.ctrl_client = tcpclient(obj.ip_addr, obj.ctrl_port, 'Timeout', obj.timeout);
obj.connected = true;
obj.send_control_command([uint8('INIT'), zeros(1,124,'uint8')]);
obj.set_center_freq(obj.freq_hz);
obj.set_if_gain(obj.gain);
end
function close(obj)
if obj.connected
try
write(obj.data_client, uint8('q'), 'uint8');
catch
end
try
write(obj.ctrl_client, [uint8('EXIT'), zeros(1,124,'uint8')], 'uint8');
catch
end
end
obj.data_client = [];
obj.ctrl_client = [];
obj.connected = false;
end
function delete(obj)
obj.close();
end
function send_control_command(obj, msg_bytes)
if ~obj.connected
error('KrakenSDR is not connected');
end
write(obj.ctrl_client, uint8(msg_bytes), 'uint8');
reply = obj.recv_exact(obj.ctrl_client, 128);
status = char(reply(1:4));
if ~strcmp(status, 'FNSD')
error('Control command failed, reply=%s', status);
end
end
function set_center_freq(obj, freq_hz)
if ~obj.connected
error('KrakenSDR is not connected');
end
obj.freq_hz = uint64(freq_hz);
freq_bytes = typecast(uint64(obj.freq_hz), 'uint8'); % little-endian
cmd = [uint8('FREQ'), freq_bytes, zeros(1,116,'uint8')];
obj.send_control_command(cmd);
end
function set_if_gain(obj, gain_in)
if ~obj.connected
error('KrakenSDR is not connected');
end
gain_list = obj.normalize_gain(gain_in);
obj.gain = gain_list;
clipped = zeros(1, obj.num_channels, 'uint32');
for k = 1:obj.num_channels
[~, idx] = min(abs(obj.valid_gains - gain_list(k)));
closest = obj.valid_gains(idx);
clipped(k) = uint32(round(closest * 10));
end
gain_bytes = typecast(clipped, 'uint8'); % little-endian
cmd = [uint8('GAIN'), gain_bytes, zeros(1, 128 - (obj.num_channels + 1)*4, 'uint8')];
obj.send_control_command(cmd);
end
function bytes = recv_exact(~, client, nbytes)
bytes = zeros(1, nbytes, 'uint8');
received = 0;
while received < nbytes
available = client.NumBytesAvailable;
if available <= 0
pause(0.001);
continue;
end
to_read = min(nbytes - received, available);
chunk = read(client, to_read, 'uint8');
bytes(received + 1 : received + numel(chunk)) = chunk;
received = received + numel(chunk);
end
end
function header = decode_header(obj, header_bytes)
if numel(header_bytes) ~= obj.HEADER_SIZE
error('Invalid header size');
end
p = 1;
header.sync_word = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
header.frame_type = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
hw = char(header_bytes(p:p+15));
header.hardware_id = deblank(char(hw(hw~=0))); p = p + 16;
header.unit_id = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
header.active_ant_chs = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
header.ioo_type = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
header.rf_center_freq = typecast(uint8(header_bytes(p:p+7)), 'uint64'); p = p + 8;
header.adc_sampling_freq = typecast(uint8(header_bytes(p:p+7)), 'uint64'); p = p + 8;
header.sampling_freq = typecast(uint8(header_bytes(p:p+7)), 'uint64'); p = p + 8;
header.cpi_length = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
header.time_stamp = typecast(uint8(header_bytes(p:p+7)), 'uint64'); p = p + 8;
header.daq_block_index = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
header.cpi_index = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
header.ext_integration_cntr = typecast(uint8(header_bytes(p:p+7)), 'uint64'); p = p + 8;
header.data_type = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
header.sample_bit_depth = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
header.adc_overdrive_flags = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
header.if_gains = zeros(1, 32, 'uint32');
for k = 1:32
header.if_gains(k) = typecast(uint8(header_bytes(p:p+3)), 'uint32');
p = p + 4;
end
header.delay_sync_flag = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
header.iq_sync_flag = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
header.sync_state = typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
header.noise_source_state= typecast(uint8(header_bytes(p:p+3)), 'uint32'); p = p + 4;
% 跳过 reserved 区
p = p + obj.RESERVED_BYTES * 4;
header.header_version = typecast(uint8(header_bytes(p:p+3)), 'uint32');
end
function dump_header(~, header)
fprintf('--- IQ Header Info ---\n');
fprintf('Sync word: %u | Version: %u\n', header.sync_word, header.header_version);
fprintf('Frame type: %u | Unit ID: %u\n', header.frame_type, header.unit_id);
fprintf('RF Freq: %.2f MHz | IQ Freq: %.2f MHz\n', ...
double(header.rf_center_freq)/1e6, double(header.sampling_freq)/1e6);
fprintf('Channels: %u | CPI Length: %u\n', header.active_ant_chs, header.cpi_length);
fprintf('Sample bit depth: %u\n', header.sample_bit_depth);
fprintf('----------------------\n');
end
function [iq, header] = receive_iq_frame(obj)
if ~obj.connected
error('KrakenSDR is not connected');
end
header_bytes = obj.recv_exact(obj.data_client, obj.HEADER_SIZE);
header = obj.decode_header(header_bytes);
obj.iq_header = header;
if obj.debug
obj.dump_header(header);
end
payload_size = double(header.cpi_length) * double(header.active_ant_chs) * 2 * double(header.sample_bit_depth) / 8;
if payload_size <= 0
iq = [];
return;
end
payload = obj.recv_exact(obj.data_client, payload_size);
% Python 版本按 complex64 解释
% 即每个复数样本 = float32 I + float32 Q
float_data = typecast(uint8(payload), 'single');
if mod(numel(float_data), 2) ~= 0
error('Payload cannot be parsed into complex float32 pairs');
end
I = float_data(1:2:end);
Q = float_data(2:2:end);
iq_complex = complex(I, Q);
iq = reshape(iq_complex, double(header.cpi_length), double(header.active_ant_chs)).';
end
function [iq, header] = get_iq_once(obj)
if ~obj.connected
obj.connect();
end
write(obj.data_client, uint8('IQDownload'), 'uint8');
[iq, header] = obj.receive_iq_frame();
end
function [iq, header] = get_data_frame(obj, max_retry)
if nargin < 2
max_retry = 20;
end
if ~obj.connected
obj.connect();
end
for k = 1:max_retry
write(obj.data_client, uint8('IQDownload'), 'uint8');
[iq, header] = obj.receive_iq_frame();
if header.frame_type == obj.FRAME_TYPE_DATA
return;
end
end
error('Failed to get DATA frame within max_retry');
end
end
end