GNU Radio
https://www.gnuradio.org/
OSSの信号処理ランタイム&開発ツールキット。
ソフトウェア無線(Software Defined Radio; SDR)とかやるやつ。
https://wiki.gnuradio.org/
https://github.com/gnuradio/gnuradio
(Wikiのブロック解説ページにはGitHubへのリンクがあったりするが、main ブランチでなく master ブランチの古いコードを指していることがあるので注意)
FAQに載ってる前提知識
https://wiki.gnuradio.org/index.php?title=FAQ#Which_concepts_do_I_need_to_know_to_start_using_GNU_Radio?
* Sampling rates / sampling theorem
* (Equivalent) complex baseband, Complex Numbers, Negative Frequency
* Digital signal processing (e.g. FIR filters)
* Wireless Communications
* Frequency/Phase/Timing Offsets
基礎概念
GNU Radio は、 Block を接続した Flowgraph でDSPを組み立てるフレームワーク
GNU Radio Companion はFlowgraphをGUIで構築して、Python or C++ コードに変換して実行するツール
アイテム群がブロック間を流れていくパイプライン
ストリームを何が流れてるかわからないし、サンプルって言うのは微妙じゃない?ということらしい
データ型
信号はもっぱら complex (complex float32) や float32 が使われる
信号処理においてベースバンド信号をI/Q信号で扱うことが多く、これを GNU Radio では complex 型で扱っている
https://wiki.gnuradio.org/index.php?title=IQ_Complex_Tutorial
バイト列は byte 型(のストリーム)で表現する
ビットストリーム的なものはないので、 Pack K Bits / Unpack K Bits で合成・分離する
ストリーム処理
各ブロックは、入力からn個のアイテムを読んで、処理して、出力にm個のアイテムを書き込む
後述するスライドを見るに、出力ポートだけにバッファがあって後段のブロックはみんなそこを読んでる気がする?
入力が供給されない時や出力が詰まっている時は連鎖的に"待ち"に入る
多くの場合、実時間基準の固定レートや割り込みで動く、ハードウェア寄りの入出力ブロックが発端になる
逆に Signal Source や GUI Time Sink などはそういった制限がないので、CPUの許す限り動き続ける
これらの samp_rate パラメータは、アイテム列の解釈を変えるだけ
処理クロック自体を落としたいなら Throttle で実時間ベースのレートリミットをかける
連鎖的に詰まるので、大抵は実線でつながっているフローのどこかに1個入れればよい
GUI Time Sink で複数入力とかにしても、全ての入力で必要なアイテムが揃うまで動かない
破線は Message Passing の接続なので別
ちなみにこのブロックは 平均レートを考慮した時に、このチャンクはいつまで遅延させるべきか という感じで動くので、例えば1Hz指定で60アイテムがまとめて届いたら、60秒間なにも出力しない。これを防ぐには Limit パラメータを指定して、チャンクを分割させる必要がある。
decimator / interpolator 系のブロックは実際に流れるアイテム数が増減する
参考: Sample Rate Tutorial, Scheduler Details
Stream Tags
ストリームを流れる各アイテムに key-value (どちらもPMT)の付加情報を添付することができる
構造的には、ストリームのバッファとは別にタグリストを持っていて、ブロックを通過する時にはスケジューラがいい感じに出力側に propagation してくれるっぽい(これもさっきの Scheduler Details にちょっと話がある)
Message Passing
ストリームのパイプラインから独立して、別のブロックに Polymorphic Types (PMT) の値を送信できる仕組み
pmt_t 型って要するにバリアント型なので…よくも悪くもブロック間の互換性が見えづらい…
よくある?メッセージ: pair (key, value)
Message Pair to Var / Variable to Message で Variable との相互変換ができる
QT GUI Message Edit Box で is_pair=True にした時の入出力
Message Meta Value to Pair で後述するPDUのメタデータを抽出
よくある?メッセージ: PDU = pair (metadata_dict, uniform_vector)
PDU to Tagged Stream / Tagged Stream to PDU で相互変換できるとか
Tagged Stream とはパケットの先頭に packet_len などのタグをつけてパケット境界がわかるようにしたStreamのことっぽい。これを扱うblockのベースクラスがあるらしい。
Socket PDU の入出力とか
メタデータがあったりなかったりするバイナリメッセージ、として使われてる感?
それ以外
Probe Rate は dict (("rate_now", v1), ("rate_avg", v2), ...) を投げる
Wikiには PMT dictionaries are lists of key:value pairs. とあるが、この list とは standard vector や uniform vector のことではなく、pair の入れ子で作られたリスト構造のことっぽい
pmt_length などが複数の型に使えることを除けば、PMTの大半の型に互換性はないのだが、3.10時点ではdictだけpairを継承しているように見える
なんにせよただの dict メッセージから値を取り出すブロックは標準では存在しなさそうで、Message Debug に出力したりZeroMQに流したりするほかは、自分で書くしかないかも: GNU Radio#68a0b4840000000000ea4ec2
QT GUI Message Edit Box は is_pair=False の時、 (value) をvalで受けて、msgから投げる
GUI操作
Companion
ブロックのパラメータにはPythonの式が書ける(結局Pythonコードに変換されるやつなので)
例 https://wiki.gnuradio.org/index.php?title=Python_Variables_in_GRC
numpyとか使いたかったら Import ブロックを配置する
ブロックのパラメータに変数や式を指定した場合に、式と値どちらを表示するかは、 View > Show Parameter ... in Block で変更できる
パラメータ名に下線がついているやつは、実行時に変更できるパラメータ
https://wiki.gnuradio.org/index.php?title=Guided_Tutorial_GRC#Variable_Controls
例えば QT GUI Range 由来の Variable を参照した場合に、下線がついてないパラメータだとスライダーを操作しても変更が反映されない
1つ以上のブロックを選択した状態で上下キーを押すとデータ型を変えられる
QT GUI
QT GUI 〜 ブロックで、ウィンドウに入力コントロールやビジュアライザを配置できる
デフォルトでは謎の順序(?)で上から順に並べられるが、 GUI Hint というプロパティで配置を制御できる
GUI Time Sink とかは、左ドラッグで範囲拡大とか、右クリックでなんか(なに?)とか、中クリックでメニューとか
ブロック
Variable と Parameter
2つの違いは、サブモジュール的な Hierarchical Block を作った時に、その中に閉じているか全体のフローグラフで共通になるか、らしい
https://wiki.gnuradio.org/index.php?title=Hier_Blocks_and_Parameters
入出力
ジェネレータ系
Signal Source: 波形ジェネレータ
Constant Source: 定数を延々流すソース。zero なら Null Source でもよい。
Vector Source: vector なり list なりの要素を順番に流すソース
ハードウェア系
Audio Source / Sink: マイク / スピーカー
Virtual Source / Sink: ポータル
その他SDRデバイス
GUI系
QT GUI 〜
QT GUI Sink: 4つの詰め合わせ
Frequency Sink: スペアナ
Waterfall Sink: スペクトログラム
Time Sink: オシロスコープ
Constellation Sink: IQコンスタレーション…complex型を複素平面にプロットするやつ
Video SDL Sink: 映像を (Y) or (Y, U/V) or (Y, U, V) の入力ポートから映像を表示する
その他
(Wav) File Source / Sink
Null Sink: /dev/null。出力ポートを埋める必要がある時に。
ハードウェア入出力を使わない場合は Throttle を入れる
流速は Probe Rate で確認できる
code:dict_to_pdu.py
from gnuradio import gr
import pmt
class blk(gr.basic_block):
def __init__(self, payload_key: str=""):
gr.sync_block.__init__(
self,
name='dict to PDU',
in_sig=None,
out_sig=None
)
self.message_port_register_out(pmt.intern('msg_pdu'))
self.message_port_register_in(pmt.intern('msg_dict'))
self.set_msg_handler(pmt.intern('msg_dict'), self.handle_msg)
self._payload_key = pmt.intern(payload_key)
def handle_msg(self, msg):
value = pmt.dict_ref(msg, self._payload_key, pmt.PMT_NIL)
if pmt.is_real(value):
vec = pmt.make_f32vector(1, pmt.to_python(value))
elif pmt.is_complex(value):
vec = pmt.make_c32vector(1, pmt.to_python(value))
elif pmt.is_integer(value):
vec = pmt.make_u8vector(1, pmt.to_python(value))
if vec:
self.message_port_pub(pmt.intern('msg_pdu'), pmt.cons(msg, vec))