Pythonではじめるソフトウェア無線
Taisuke Yamada
@tyamadajp
今日の内容
● ソフトウェア無線って何?
● どんなことができるの?
● ちょっとだけ電波や通信の話
● フレームワークやライブラリ
● 沼への始めてみたい人への案内
ソフトウェア無線って?
● 従来はハードウェアで実現されていた要素が
● ソフトウェアによって実現されたもので
● 技術の発展により理論上でのみ可能とされていたことが
現実に可能になった
目には見えてない電波の世界
https://www.nict.go.jp/publication/NICT-News/0605/research/index.html
電波時計からCTスキャンまで下から上まで日々使っていても、
専用機器を介して使うだけ。SDRの機材性能の範囲で、電波は
もっとダイレクトに見えるようになる。
「電波」の範囲
例:SDRソフトで電波をみる
既成のSDRソフトウェアの例。ビジュアルに信号を見ながら
周波数や復調方式を選択して簡単に信号をデコードしたり保存できる。
ソフトによっては再生(=送信!)できるものも。
https://cubicsdr.com/
例:フライト状況の観測
航空機は飛行中に位置情報を電波で飛ばしているので、自分で
受信したデータをマッピングしたり、みんなで持ち寄って世界の
フライト状況の可視化をやっていたりする。
https://globe.adsbexchange.com/
例:衛星画像の受信
https://pietern.github.io/goestools/guides/minimal_receiver.html
各国の気象衛星は様々な周波数帯で刻々と気象データを地球へ
送っている。難易度は衛星によって異なるが、米国のNOAA衛星や
GEOS衛星がターゲットとして多く選ばれている(なお、ひまわりは
難しいらしい)。
例:銀河系の観測
https://www.rtl-sdr.com/wp-content/uploads/2020/07/Big-Galaxy-Explanation.pdf
水素は宇宙で一番多い原子で、励起状態から戻る際に1420MHzの
電波(波長から21cm線と呼ぶ)を放出する。なので、これを観測して
銀河の星々の活動をマッピングできる。
例:謎ガジェット無線通信の解析
すべての無線デバイスは電波で通信を行っている。
なので、これを観測して解読できれば、通信のデバッグや同一機能の
自前デバイスのソフト実装ができる。
実はこれしかしてないので、他の魅力的な色々はできてない・・・orz
有線だと、見える気がする
有線だったり通信の高いレイヤだとロジアナやプロトコルアナライザで
見るのは比較的容易で、身近な感じ(読み解けるとは言ってない)
無線だと・・・
何か飛んでる!魔法!
イラスト出典:いらすとや: https://www.irasutoya.com/
魔法の中身
何が何だかわからないよ!
わかりたい
0000001111111110101001100000000000000011011011011011010111
どういうプロセスを経て、情報が電波となり、電波が情報になるのか?
今日紹介するもの
基本的にこの分野でもっとも使われているGNURadioの紹介で、
その上で他のものに触れる形で進行>本日
Python: どう活用されてる?
● GNURadio (+ GNURadio Companion)
– C++/Pythonが主力言語
– DSPや制御ブロックをPythonで書いて拡張できる
– 機能ブロックを接続してPythonアプリを生成
● PothosFlow
– C++/Pythonが主力言語
– ほぼGNURadio(GRC作者がGRに不満で作った)
● Universal Radio Hacker
– Pythonで書かれた解析用SDRアプリケーション
DSPコアはさすがにC++が強いものの、それでもNumPyやらCythonが
色々なところに登場して活用されている
GNURadio Companion
● 2001年:GNURadioプロジェクト開始
– 信号処理フレームワーク
– 豊富なDSPブロックのライブラリ
– それらDSPブロックを実行するスケジューラ
– 多数の各種SDRデバイスのIOドライバ
● 2009年:GRCでビジュアル開発が可能に
– シグナルフロー図で機能ブロックを連結
– 図からPythonコードを生成して実行・検証
GNURadio Companion
単純なものから高水準なものまで、ブロックを接続してパラメータを
決めていくだけで信号処理・無線通信の実験ができる
GRC生成コードの例(抜粋)
PyQtなアプリだったりGUI抜きのコードを生成する。
中身はブロックをひたすら生成・結線する親ブロックを定義し、
最後にそのインスタンスを作って実行するもの
やってみよう
一番簡単な「Lチカ」、「Hello, world」的なフローグラフ。
Signal Sourceで1KHzの信号を生成して、可視化用のQT GUI Time Sinkで
それを受けている。間のThrottleはCPU過剰消費防止用。
やってみよう
一番簡単な「Lチカ」、「Hello, world」的なフローグラフ。
Signal Sourceで1KHzの信号を生成して、可視化用のQT GUI Time Sinkで
それを受けている。間のThrottleはCPU過剰消費防止用。
GRCのPythonコード生成
GRCのフローグラフ実行は、実はPyQtアプリを生成して、それを
実行している。生成されたコードを保存しておけば、以後はGRCなしで
実行できる(gnuradioライブラリ自体は必要)
$ python top_block.py
FMラジオ
簡単な操作機能付きのFMラジオ。
信号を3.2MHzでサンプリングし、内部レートは320KHzで
処理し、最後のオーディオ出力段では32KHzで出力する構成。
FMラジオ
受信した3.2MHz範囲に
80.0, 81.3, 82.5MHzの
3つのFM局が見えている
ソフトなので増設も簡単
1階建てを後から2階建てにしてくれ(=2局同時受信して)と
言われても安心!
大サービスで6局同時受信!
スピーカー出力は1つなのでセレクタが入ってしまうものの、
録音するなら全局同時に録音できる(Wav File Sinkを入れる)
大サービスで6局同時受信!
原理的にはその通りだが、そうはうまくいかない
ハードの限界がソフトの限界
サンプリングレートが3.2MHzなので、指定した周波数の上下1.6MHzの
範囲しか信号を得ていない。機材の限界もあり、もっと高いレートで
サンプリングができるSDR受信機が必要。
※使用したADALM-PLUTOは搭載ICは61MSPS程度までいけるが
 USB転送がネックで4MSPS以上は信号が落ちるという背景
受信範囲にあったFM局は3局位・・・
広帯域が見えるということ
捕捉したい信号の帯域の倍以上のサンプリング速度が必要になる(※)。
また、高速な信号ほどCPUやバスに負荷をかけるので、見たい信号に
見合ったSDR機材やホストPCの性能が必要になる。
信号帯域20MHzの
無線LANの通信
※実機では直行変調で2つのADCを駆動するためADC能力は1倍でよかったりする
ちょっとデータ型の話
色の違いはfloatやcomplexといったデータ型の違い。
フローグラフのSDRデバイスとのIOや信号処理部分では、complexが
利用されることが多い。最後の出力段は音声レベルなどの実信号になる
この色の違いは、なんだろう?
なんで複素数?まずはAM/FMから
AMは振幅が変わる
一番おなじみのAMとFM。
この2つは振幅の変化・周波数の変化に情報を割りつけている方式。
信号を、これら以外の軸でも考えることはできるか?
FMは周波数が変わる
実信号→解析信号への分解
実信号を2つの信号成分に分解 この2つの信号成分で
作られるIQ平面で信号を考える
Q成分
I成分
信号を数式で考えると複素数が出てくるものの(※)、
ここでは定性的に「信号を2成分に分解して、2軸にマップして
分析すると幸せになれる」位で流して下さい
Q成分
I成分
※いい説明ができずに挫折しましたが、末尾の補記を参照下さい
実信号
この考え方の嬉しい点
ある瞬間における信号
Q成分
I成分
実信号
実信号と違い、IQ平面では
IとQの値から角度=位相を
即計算できる。
位相計算が簡単だと、位相や位相の変化に情報を乗せやすい。
また、位相(角度)の変化から角速度=周波数も即座に出せる。
こういった背景から、IQデータで信号が扱われることが多い。
※図の波形は違いを見せるためのもので、実際の形とは異なります
SDR: この分解処理=直行変調マシン
実信号
Q成分
I成分
ここまではSDR機材が頑張るから、デジタルデータ化した後はソフトで
頑張って、という話になる
話を戻す:GNURadioを拡張しよう
アプリをGRCで生成できる他、PythonでGNURadioブロック自体を
書いて拡張できる。Embedded Python Blockを追加し、設定画面から
エディタを開きブロックのクラス定義を編集すると即反映する。
GNURadioブロックの入出力構造
Dataは文字通りで信号データ、Tagは信号データの各所にマークを
付け後続ブロックの目印とするメタデータ、そしてMessageは
ブロックに対する制御メッセージを送るためのもの。
これら3つをブロックで生成したり受け渡しつつ、ブロック
パラメータに応じた処理をしてゆく形となる。
Message In Message Out
Data In Data Out
010101110000000001010101010
Tag(name: value)
Data
ブロックの例(イニシャライザ)
入出力データ量の比が1:1のブロック
このdocstringがGRCで表示
これらデフォルト引数が
ブロック設定項目となる
GRCでのブロック名
信号の入出力ポートと型の定義
複数ポートの場合は配列に追加
メッセージポートとハンドラ
ブロックの例(Data, Message)
信号処理の本体。出力用バッファの中身を更新する形で処理する
登録したメッセージハンドラ。
メッセージはpmtライブラリで生成するpmtオブジェクトとして交換する
ブロックの例(Tag)
タグの値を強制更新する自作ブロックでの処理。
入ってきた信号の範囲(ウインドウ)にtag_nameのタグがあるかを
調べ、該当する信号値にtag_valueを値とするタグをセットしている。
信号自体はパススルー
入ってきたタグは
後段にリレーしない
GNURadioでパケット通信
理論的な部分はすっとばしているものの、フローグラフを読む
概略知識はここまでで出揃ったので、デジタルデータをどのように
パケットとして変復調できるのか追いかけてみよう
※発表時間の都合で、細かい詳細は補記の方を参照下さい
GFSK変調でデータ送受信(全体)
GFSK変調でデータ送受信
パケットデータの生成
GFSK変調でデータ送受信
変調→送信→受信→復調
GFSK変調でデータ送受信
シンボル同期とフレーム検出
ここからはGNURadioに対する比較、という形の紹介なので
駆け足になります。
Pothos
● GNURadio/GRCの代替として開発
– GRC作者がGRの機能に不満があり着手したらしい…
● おおむねGR/GRCと同じ
– シグナルフロー図で機能ブロックを連結
– APIでC++/Pythonでアプリや拡張開発可能
● 特徴的な違い
– Pythonコードジェネレータは未実装
– 画面操作性はGRCよりもこなれている
● 変更がライブで反映、実行結果が止めても残るなど
– 統一的なSDR機器のIOドライバがある(SoapySDR)
● ネットワーク透過なIOもできる
PothosFlow
機能的にはGNURadioと同等(GRのブロックも使える)
可視化や操作UIはPyQtな別窓が開く形態ではなく、シートに
自由配置する方式。別シートにも置け画面構成の自由度が高い
Pothos拡張もPythonで
上のフローグラフ自体はPython blockをテストしただけのものだが、
PothosでもPythonでブロックを拡張できる。
ただし、登録はGUIからではなく指定フォルダに配置の必要がある。
Windows: %APPDATA%/Pothos/modules/
Linux: ~/.config/Pothos/modules/
PothosアプリをPythonで
コードジェネレータは
ないため自分で書く。
GRと同様の
 1. ブロックを設定
 2. ブロックを接続
 3. 実行開始
の構成。API自体はかなり
クリーンで読みやすい。
SoapySDR (from PothosWare)
ここまでだと「PothosじゃなくてGRでもいいか?」という気にも
なるが、各種IOドライバの統合APIとしてSoapySDRが入っているのが
大きなポイント。これを使えば自動で各種SDR対応になり嬉しい
https://github.com/pothosware/SoapySDR
無線通信のデコードは日暮れて途遠し
さっきのこれ、えらい手間がかかるなと思いませんでした?
最小構成かつすべてが既知でこれなので、ヘッダ・ペイロード・変調が
未知の信号解析だと、どれほどの手間になることか・・・
本番はビット列が特定できてから
ビット列が取れてから資料などと照合したり長さしながら読むわけで、
ビット列の採取であんなに手間がかかっていたら大変すぎる!
0000001111111110101001100000000000000011011011011011010111
そんなあなたにURH
フローグラフすら描くことなく半自動でキャプチャした信号の
ノイズ除去、復調、シンボル同期がなされる!範囲指定やパラメータを
切り替えたらその場で新しい結果が即座に見える。
ビット抽出ができたら、読み進めるだけ。
つじつまが合わない場合はパラメータを変えて
再度抽出を繰り返す。URHを使うとこのサイクルが
100倍速位に。この素晴らしいアプリはどうやって
開発されているのか?
もちろんPython
$ pip install urh
URHもやはりプラグイン拡張が可能で、当然Pythonが開発言語。
しかしその詳細は力が及ばなかったので今回は報告は見送りです・・・
まとめます
● 従来は「ラジオ用」等の専用機器を介したアクセス
● SDRで無線へのアクセス性が質的に変わった
– 振幅・位相といったレベルで任意の計測が可能に
– 商用機材(H/W)がなくてもアクセス可能に
● GNURadio/GRCはビジュアルPython開発環境
– フローグラフ=Pythonアプリケーション
● PothosWareも強力な信号処理開発環境
– SoapySDRを使えば未来のSDR機材まで含めて対応
● Universal Radio Hackerは夢のリバエン環境
この分野、あらゆる所にPythonが使われています。
あなたもPythonでソフトウェア無線をはじめてみませんか?
END
補記
周波数帯の呼び名
周波数 波長 呼称 補足
~3KHz 100km~ 極超短波(ULF, SLF, ELF) Ultra, Super,
Extremely Low
3~30KHz 10~100km VLF、超長波 Very Low
30~300KHz 1~10km LF、長波 Low
0.3~3MHz 0.1~1km MF、中波 Medium
3~30MHz 10~100m HF、短波 High
30~300MHz 1~10m VHF、超短波、メートル波 Very High
0.3GHz~
3GHz
0.1~1m UHF、極超短波
マイクロ波
L, S, C, X, Ku, K,
Ka, V, W, G
などのバンド名での
分類も使われる
3~30GHz 1~10cm SHF、センチメートル波
30~300GHz 1~10mm EHF、ミリ波
0.3~3THz 0.1~1mm THF、テラヘルツ波
3THzまでが電波という扱い。ここから先は赤外線、可視光、紫外線、X線、ガンマ線と続く
補記:SDRとデジアナ境界
SDR:物理の制約はやっぱりある
実信号
Q成分
I成分
アンテナ
フィルタ
増幅器
フィルタ
Local Oscillator
局発/局部発振器
IF / 中間周波数
RF
ADC増幅器
LO - RF = IF
ここまで
アナログ
ここから
デジタル
※どこが境界になるか(ADCがどこに置かれるか)は機材の設計による
※ADC性能やバス帯域との綱引きでそれぞれ結構違う構成で出ている
補記:実信号→解析信号への分解
わかりやすいスライドが作れないかと考えた挙句に断念。
作ったメモの内容まで貼っておきます。
実信号→解析信号への分解
この切り捨て処理がポイントで、「負の周波数」についての解釈を
見ることもありますが、結局の所は解析に必要十分な成分だけ残すと
いう操作です。ここで数学的な等価性は失います。
実信号→解析信号への分解
このような形に分解すると、周波数の算出や位相変化の検出といった
計算処理が非常に容易になり、そういった信号要素の変化に情報を
乗せる無線通信では不可欠なものになっています。
補記:パケットデータの生成
パケットデータの生成
ダミーデータの生成
01 01 01 01 01 01 01 01
00 00 00 00 00 00 01 01
という16byteの反復
パケットデータの生成
16byte単位でタグを打ち、ペイロード範囲の目印とする
01010101010101010000000000000101 0101010101010101...
Tag(packet_len: 16) Tag(packet_len: 16)
パケットデータの生成
入ってきたタグからペイロード長を得て、
それを含むフレームヘッダを生成する
AA00100010 AA00100010
Tag(packet_len: 16) Tag(packet_len: 16)
01010101010101010000000000000101 0101010101010101...
Tag(packet_len: 16) Tag(packet_len: 16)
= AA
パケットデータの生成
フレームヘッダとペイロードの2つの
ストリームを、タグをキーに合成する
AA0010001001010101010101010000000000000101 AA001...
Tag(packet_len: 16)
= AA
Tag(packet_len: 16)
パケットデータの生成
デバッグ用。バイトデータをビット
列に落として、可視化している
= AA
ヘッダ ペイロード
信号表示を安定させるにはタグトリガ機能を使い、
指定のタグが入ったタイミングを先頭に描画させる
補記:変調→送信→受信→復調
変調→送信→受信→復調
変調→送信 受信→復調
変復調はGNURadioのブロックにおまかせなので、非常に簡単。
本当に送受信する場合はこの間にSDR機器を入れる。
間のTag Gateはタグが受信側にまで伝播しないようにフィルタするもの。
FSKのコンステレーション 復調したベースバンド信号
(まだbitになってない)
補記:シンボル同期とフレーム検出
シンボル同期とフレーム検出
bit(AA0010001001010101010101010000000000000101) ...
ベースバンド信号を処理し、
シンボル=ビット列に変換
シンボル同期とフレーム検出
bit(01010101010101010000000000000101) ...
入ってきたビット列を走査し、所定のビット列(0xAA)を
検知したらヘッダをデコードし、タグ付きペイロードを吐く
= AA
Tag(len_key2: 128) 128bit (16byte) payload
シンボル同期とフレーム検出
01010101010101010000000000000101 010101010101010...
ここまで数値的にはfloatで流れていたデータを
タグを境界目印にしつつビット→バイトと変換
Tag(len_key2: 128) Tag(len_key2: 128)
シンボル同期とフレーム検出
先頭8192byteだけ取り出して
ファイルに保存
受 信 完 了
元々送出したバイト列と同じものが8KB続いている
ブラックボックスが少しオープンに
0000001111111110101001100000000000000011011011011011010111
フローグラフ=~Pythonアプリなので、これで原型を作って
開発を進めれば、無線信号の処理をPythonで作れる!
Getting Started with SDR in Python

Getting Started with SDR in Python