Skip to content

Commit

Permalink
Merge pull request #3 from hcab14/master
Browse files Browse the repository at this point in the history
GPS timestamps for IQ mode
  • Loading branch information
jks-prv authored Nov 24, 2017
2 parents 894ca41 + 4149f29 commit 103aaba
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 15 deletions.
9 changes: 6 additions & 3 deletions kiwiclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,13 @@ def _process_aud(self, body):
data = body[6:]
rssi = (smeter & 0x0FFF) // 10 - 127
if self._modulation == 'iq':
gps = dict(zip(['last_gps_solution', 'dummy', 'gpssec', 'gpsnsec'], struct.unpack('<BBII', data[0:10])))
## gps['gpsnsec'] = struct.unpack('<I', data[6:10])[0]
data = data[10:]
count = len(data) // 2
data = struct.unpack('>%dh' % count, data)
data = struct.unpack('>%dh' % count, data)
samples = [ complex(data[i+0], data[i+1]) for i in xrange(0, count, 2) ]
self._process_iq_samples(seq, samples, rssi)
self._process_iq_samples(seq, samples, rssi, gps)
else:
samples = self._decoder.decode(data)
self._process_audio_samples(seq, samples, rssi)
Expand All @@ -247,7 +250,7 @@ def _on_sample_rate_change(self):
def _process_audio_samples(self, seq, samples, rssi):
pass

def _process_iq_samples(self, seq, samples, rssi):
def _process_iq_samples(self, seq, samples, rssi, gps):
pass

def _setup_rx_params(self):
Expand Down
53 changes: 41 additions & 12 deletions kiwirecorder.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@

import kiwiclient

def _write_wav_header(fp, filesize, samplerate, num_channels):
def _write_wav_header(fp, filesize, samplerate, num_channels, is_kiwi_wav):
fp.write(struct.pack('<4sI4s', 'RIFF', filesize - 8, 'WAVE'))
bits_per_sample = 16
byte_rate = samplerate * num_channels * bits_per_sample / 8
block_align = num_channels * bits_per_sample / 8
fp.write(struct.pack('<4sIHHIIHH', 'fmt ', 16, 1, num_channels, samplerate, byte_rate, block_align, bits_per_sample))
fp.write(struct.pack('<4sI', 'data', filesize - 12 - 8 - 16 - 8))
if not is_kiwi_wav:
fp.write(struct.pack('<4sI', 'data', filesize - 12 - 8 - 16 - 8))

class KiwiRecorder(kiwiclient.KiwiSDRSoundStream):
def __init__(self, options):
Expand All @@ -30,17 +31,21 @@ def __init__(self, options):
self._nf_samples = 0
self._nf_index = 0
self._num_channels = 2 if options.modulation == 'iq' else 1
self._last_gps = dict(zip(['last_gps_solution', 'dummy', 'gpssec', 'gpsnsec'], [0,0,0,0]));

def _setup_rx_params(self):
mod = self._options.modulation
mod = self._options.modulation
lp_cut = self._options.lp_cut
hp_cut = self._options.hp_cut
freq = self._options.frequency
freq = self._options.frequency
if (mod == 'am'):
# For AM, ignore the low pass filter cutoff
lp_cut = -hp_cut
self.set_mod(mod, lp_cut, hp_cut, freq)
self.set_agc(True)
if self._options.agc_off:
self.set_agc(on=False, gain=self._options.agc_gain)
else:
self.set_agc(on=True)
self.set_inactivity_timeout(0)
self.set_name('')
self.set_geo('Antarctica')
Expand Down Expand Up @@ -73,15 +78,17 @@ def _process_audio_samples(self, seq, samples, rssi):
self._squelch_on_seq = None
self._start_ts = None
return
self._write_samples(samples)
self._write_samples(samples, {})

def _process_iq_samples(self, seq, samples, rssi):
sys.stdout.write('\rBlock: %08x, RSSI: %-04d' % (seq, rssi))
def _process_iq_samples(self, seq, samples, rssi, gps):
sys.stdout.write('\rBlock: %08x, RSSI: %-04d\n' % (seq, rssi))
## convert list of complex numbers to an array
##print gps['gpsnsec']-self._last_gps['gpsnsec']
self._last_gps = gps
s = array.array('h')
for x in [[y.real, y.imag] for y in samples]:
s.extend(map(int, x))
self._write_samples(s)
self._write_samples(s, gps)

def _get_output_filename(self):
ts = time.strftime('%Y%m%dT%H%M%SZ', self._start_ts)
Expand All @@ -93,18 +100,25 @@ def _update_wav_header(self):
fp.seek(0, os.SEEK_END)
filesize = fp.tell()
fp.seek(0, os.SEEK_SET)
_write_wav_header(fp, filesize, int(self._sample_rate), self._num_channels)
_write_wav_header(fp, filesize, int(self._sample_rate), self._num_channels, self._options.is_kiwi_wav)

def _write_samples(self, samples):
def _write_samples(self, samples, *args):
"""Output to a file on the disk."""
print '_write_samples', args
now = time.gmtime()
if self._start_ts is None or self._start_ts.tm_hour != now.tm_hour:
self._start_ts = now
# Write a static WAV header
with open(self._get_output_filename(), 'wb') as fp:
_write_wav_header(fp, 100, int(self._sample_rate), self._num_channels)
_write_wav_header(fp, 100, int(self._sample_rate), self._num_channels, self._options.is_kiwi_wav)
print "\nStarted a new file: %s" % (self._get_output_filename())
with open(self._get_output_filename(), 'ab') as fp:
if (self._options.is_kiwi_wav):
gps = args[0]
sys.stdout.write('gpssec: %d' % (gps['gpssec']))
fp.write(struct.pack('<4sIBBII', 'kiwi', 10, gps['last_gps_solution'], 0, gps['gpssec'], gps['gpsnsec']))
sample_size = samples.itemsize * len(samples)
fp.write(struct.pack('<4sI', 'data', sample_size))
# TODO: something better than that
samples.tofile(fp)
self._update_wav_header()
Expand Down Expand Up @@ -152,6 +166,21 @@ def main():
dest='thresh',
type='float', default=0,
help='Squelch threshold, in dB.')
parser.add_option('-w', '--kiwi-wav',
dest='is_kiwi_wav',
default=False,
action='store_true',
help='wav file format including KIWI header (only for IQ mode)')
parser.add_option('-a', '--agc-off',
dest='agc_off',
default=False,
action='store_true',
help='AGC OFF (default gain=40)')
parser.add_option('-g', '--agc-gain',
dest='agc_gain',
type='float',
default=40,
help='AGC gain')

(options, unused_args) = parser.parse_args()

Expand Down
21 changes: 21 additions & 0 deletions proc.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
## -*- octave -*-

function [x,xx]=proc(fn) ##20171119T171520Z_30000_iq.wav
tic
[x,fs]=read_kiwi_iq_wav(fn);
toc;

idx = find(cat(1,x.gpslast)<6);
xx = {};
for i=1:length(idx)
j=idx(i);
xx(i).t = x(j).gpssec + 1e-9*x(j).gpsnsec + [0:length(x(j).z)-1]'/fs;
xx(i).z = x(j).z;
end

if length(xx) != 0
subplot(2,2,1:2); plot(mod(cat(1,xx.t), 1), abs(cat(1,xx.z))); xlabel("GPS seconds mod 1 (sec)");
subplot(2,2,3); plot(mod(cat(1,xx.t), 1), abs(cat(1,xx.z)), '.'); xlim([0.0285 0.0294]) ; xlabel("GPS seconds mod 1 (sec)");
subplot(2,2,4); plot(mod(cat(1,xx.t), 1), abs(cat(1,xx.z)), '.'); xlim(0.1+[0.0285 0.0294]) ; xlabel("GPS seconds mod 1 (sec)");
end
endfunction
122 changes: 122 additions & 0 deletions read_kiwi_iq_wav.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// -*- C++ -*-

#include <fstream>
#include <octave/oct.h>

class chunk_base {
public:
std::string id() const { return std::string((char*)(_id), 4); }
std::streampos size() const { return _size; }
private:
int8_t _id[4];
uint32_t _size;
} __attribute__((packed));

class chunk_riff : public chunk_base {
public:
std::string format() const { return std::string((char*)(_format), 4); }

private:
int8_t _format[4];
} __attribute__((packed));

class chunk_fmt : public chunk_base {
public:
uint16_t format() const { return _format; }
uint16_t num_channels() const { return _num_channels; }
uint32_t sample_rate() const { return _sample_rate; }
uint32_t byte_rate() const { return _byte_rate; }
uint16_t block_align() const { return _block_align; }

protected:
uint16_t _format;
uint16_t _num_channels;
uint32_t _sample_rate;
uint32_t _byte_rate;
uint16_t _block_align;
uint16_t _dummy;
} __attribute__((packed));

class chunk_kiwi : public chunk_base {
public:
uint8_t last() const { return _last; }
uint32_t gpssec() const { return _gpssec; }
uint32_t gpsnsec() const { return _gpsnsec; }
private:
uint8_t _last, _dummy;
uint32_t _gpssec, _gpsnsec;
} __attribute__((packed));

DEFUN_DLD (read_kiwi_iq_wav, args, nargout, "[d,sample_rate]=read_kiwi_wav(\"<wav file name\");")
{
octave_value_list retval;

const std::string filename = args(0).string_value();
if (error_state)
return retval;

std::ifstream file(filename, std::ios::binary);

octave_value_list cell_z, cell_last, cell_gpssec, cell_gpsnsec;

chunk_base c;
chunk_fmt fmt;

int data_counter=0;
while (file) {
std::streampos pos = file.tellg();
file.read((char*)(&c), sizeof(c));
if (!file)
break;

if (c.id() == "RIFF") {
chunk_riff cr;
file.seekg(pos);
file.read((char*)(&cr), sizeof(cr));
if (cr.format() != "WAVE") {
// complain
break;
}
} else if (c.id() == "fmt ") {
file.seekg(pos);
file.read((char*)(&fmt), sizeof(fmt));
if (fmt.format() != 1 ||
fmt.num_channels() != 2) {
// complain
break;
}
retval(1) = octave_value(fmt.sample_rate());
} else if (c.id() == "data") {
ComplexNDArray a(dim_vector(c.size()/4, 1));
int16_t i=0, q=0;
for (int j=0; j<c.size()/4 && file; ++j) {
file.read((char*)(&i), sizeof(i));
file.read((char*)(&q), sizeof(q));
a(j) = std::complex<double>(i/32768., q/32768.);
}
cell_z(data_counter++) = octave_value(a);
} else if (c.id() == "kiwi") {
file.seekg(pos);
chunk_kiwi kiwi;
file.read((char*)(&kiwi), sizeof(kiwi));
cell_last(data_counter) = octave_value(kiwi.last());
cell_gpssec(data_counter) = octave_value(kiwi.gpssec());
cell_gpsnsec(data_counter) = octave_value(kiwi.gpsnsec());
} else {
std::cout << "skipping unknown chunk " << c.id() << std::endl;
pos = file.tellg();
file.seekg(pos + c.size());
}
}

octave_map map;
map.setfield("z", cell_z);
if (cell_last.length() == cell_z.length()) {
map.setfield("gpslast", cell_last);
map.setfield("gpssec", cell_gpssec);
map.setfield("gpsnsec", cell_gpsnsec);
}
retval(0) = map;

return retval;
}

0 comments on commit 103aaba

Please sign in to comment.