手習ひデジタル信号処理(89) PythonからScilabへRTL-SDRデータをお裾分け

Joseph Halfmoon

前回、遅ればせながら入手のソフトウエア無線受信機RTL-SDRをPythonで読み取りできました。順当な線ではPythonで後段処理、ということになりますが、このところやっていたScilab様をお見限り、というのは寂しい限り。どうせリアルタイム処理は狙わないのでRTL-SDRのデータをScilabへもお裾分けっと。

※「手習ひデジタル信号処理」投稿順 Indexはこちら

案ずるより産むがやすしということで、長年逡巡していたRTL-SDRも使ってみたらば今のところ順調です。pyrtlsdrモジュールを使えば、PythonでIQ信号を取り出せるので、Pythonには各種の実績あるモジュールあり、それらを駆使しての後段処理に夢が広がるというわけです。

しかし度々お世話になってきたScilabを止めてしまうのも寂しいということで、今回はPythonからScilabへRTL-SDRのIQ信号をファイルで送る(勿論リアルタイム性はまったく無。バッチです。)ルートを開拓。

python側のコード

python側のコードは以下のようです。RTL-SDRを駆動して所定の分量の信号を取得し、それをScilab可読のフォーマットのテキストファイルに落とすもの。

$ python pyrtlsdr2sci.py -h

上記のように -h オプションつければ使用方法が表示されます。といっても引数に値をセットして呼び出すだけ。全てにデフォルト値が与えられているので、何も引数にセットしなければ、Tokyo FM(80MHz)を中心とした2MHz幅くらいの領域のIQ信号を262144点取得し、toScilab.csv というファイル名にてカレントフォルダに書き出します。

なお、以下のコードは暫定版っす。当然バグあり、エラーチェックなど無のもの。

#!/usr/bin/python
# coding: utf-8
### @file  pyrtlsdr2sci.py
### @brief save RTL-SDR IQ signal for scilab
###
### @author: Joseph Halfmoon
### @date:   June 6, 2023
"""
Save RTL-SDR IQ signal for scilab v0.1

input
=====
--FS     sample rate (default=2.048[MHz])
--FC     center_freq (defalut=80[MHz]
--N      number of samples (defalut=256*1024)
--FNAME  File name to save (defalut=toScilab.csv)
-V       VERSION
"""
import argparse
import csv
import numpy as np
import sys
from rtlsdr import RtlSdr

versionSTR = "pyrtlsdr2sci.py v0.1"

def parse2float(st):
    """parse string to float
    
    Returns at Fail: None
    """
    try:
        work = float(st)
    except:
        return None
    return work

def chkVal(arg, dval):
    vl = parse2float(arg)
    if vl is None:
        nLen = len(arg)
        if nLen > 1:
            bdy = arg[0:nLen-1]
            lastChar = arg[nLen-1]
            vl = parse2float(bdy)
        if vl is None:
            vl = dval
    else:
        lastChar = "0"
    return (vl, lastChar)

def freqIN(arg, dval):
    prefixes = 'mkMG'
    vl, lastChar = chkVal(arg, dval)
    pfx = prefixes.find(lastChar)
    if pfx == 0:
        ex = 1e-3
    elif pfx == 1:
        ex = 1e3
    elif pfx == 2:
        ex = 1e6
    elif pfx == 3:
        ex = 1e9
    else:
        ex = 1
    return vl * ex

def parse2int(st):
    """parse string to int
    
    Returns at Fail: None
    """
    try:
        work = int(st)
    except:
        return None
    return work

class rtlsdrBuffer(object):
    """rtlsdr buffer class
    
    read & save RTL-SDR IQ signal
    Fs : sampling Freq[Hz]
    Fc : center Freq[Hz]
    """
    def __init__(self, Fs, Fc, N):
        self.sdr = RtlSdr()
        self.sdr.sample_rate = Fs
        self.sdr.center_freq = Fc
        self.sdr.freq_correction = 60
        self.sdr.gain = 'auto'
        self.N = N
        self.buffer = np.zeros(self.N, dtype=np.complex128)

    def read(self):
        self.buffer = self.sdr.read_samples(self.N)

    def writeSCI(self, fnam):
        self.fnam = fnam
        np.savetxt(self.fnam, self.buffer, fmt='%.18e %+.18ej')

def main():
    """main program.
    
    process options
    """
    parser = argparse.ArgumentParser(description=versionSTR)
    parser.add_argument('--FS', nargs=1, help='sample rate (default=2.048[MHz])')
    parser.add_argument('--FC', nargs=1, help='center_freq (defalut=80[MHz])')
    parser.add_argument('--N',  nargs=1, help='number of samples (defalut=256*1024)')
    parser.add_argument('--FNAME',  nargs=1, help='File name to save (defalut=toScilab.csv)')
    parser.add_argument('-V', dest='VERSION', help='Show Version, then exit', action='store_true', default=False)
    args = parser.parse_args()

    if args.VERSION:
        print( versionSTR )
        sys.exit(0)

    Fs = 2.048e6
    if args.FS is not None:
        Fs = freqIN(args.FS[0], Fs)
    Fc = 80e6
    if args.FC is not None:
        Fc = freqIN(args.FC[0], Fc)
    N = 256*1024
    if args.N is not None:
        tmpN = parse2int(args.N[0])
        if tmpN is not None:
            N = tmpN
    FNAME = "toScilab.csv"
    if args.FNAME is not None:
        FNAME = args.FNAME[0]

    sdr = rtlsdrBuffer(Fs, Fc, N)
    sdr.read()
    sdr.writeSCI(FNAME)

    sys.exit(0)

if __name__ == "__main__":
    main()

上記コードを書くにあたって、numpyのndarray(複素数が詰まっている)のファイル出力の方法が分かってなかったので、以下のページにお世話になりました。

numpy.savetxt

以下のようなコマンドラインで信号データが、–FNAMEで指定のファイルに出力されます。python_operations

 

出力ファイルとScilabでの読み込み

出力されたcsvファイルをテキストエディタで開くとこんな感じです。1行毎に複素数ひとつが書き出されてます。ポイントは虚数単位が “j” と表現されているところですかね。Python(numpy)側のデフォルトは 電気電子風に j みたいです。scilab_txt

一方、Scilabではキホンは i を使っている(コンソールからの手入力は%iだがファイル上では i で良い)のですが、試したところファイル上では j でも虚数単位と解釈して読み込んでくれました。読み込んだ後は勿論 i に統一されてます。心が広いなScilab。

Scilba上で、Python出力のファイルを読み込む処理シーケンスが以下に。scilab_operations

読み込んだ配列をPSDプロットしてみました。こんな感じ。scilab_psd

よめとるみたいやね。

手習ひデジタル信号処理デジタル信号処理(88) pyrtlsdr、PythonでRTL-SDRの信号を処理 へ戻る

手習ひデジタル信号処理(90) PythonでRTL-SDRデータをデシメーション へ進む