PicoScenes
OS:Ubuntu 22.04
NIC:intel ax211内蔵のノートPCとintel NUC
Jetsonでも試したが、インストールできない(公式ドキュメントに*We don’t support ARM CPU, despite planned.*と記載あり)
下記のマニュアル通りにやればできる。
- 最初にユーザ名などを登録するように指示が出る。有料版のライセンス登録するときや開発者にエラー発生時のログを渡すときに必要。
-
PSLP_UICというコマンドを実行すると登録できる- 大学名:XXX Univ, Japanなど
- コンピュータ名:ホスト名など
- research leader:教員名など
-
- Linux kernel versionでエラーが出る場合は指定されたversionを起動する必要がある。
エラーメッセージにlinux-kernelのバージョンが指定されている場合、下記の手順でインストールおよび起動可能
6.5.0-15-genericが指定された場合
-
カーネルインストール :
sudo apt install linux-image-6.5.0-15-generic -
grubで起動するカーネルを指定
-
/etc/default/grubを編集:GRUB_DEFAULT=savedに変更 -
sudo update-grubで反映 - デフォルトの指定:
sudo grub-set-default "Advanced options for Ubuntu>Ubuntu, with Linux 6.5.0-15-generic"
-
PythonToolbox
↓のツールが公式。手順通りインストールできる。
-
個人的にforkしたもの
- https://github.com/exyrias/PicoScenes-Python-Toolbox.git
- ビルドとローカルへのインストール方法
# gitクローン(recursiveが必須) git clone https://github.com/exyrias/PicoScenes-Python-Toolbox.git --recursive # 必要なもの pip install cycler Cython kiwisolver matplotlib numpy Pillow pyparsing python-dateutil six setuptools # ビルド python3 setup.py build_ext --inplace python3 setup.py install- 変更内容
- Jupyter, ipythonで使えるようにした
- ファイルオープンに失敗したときの処理をexit→例外処理に
- データの部分的なロード
- 初期化時に開始と終了位置(単位バイト)を引数で受け取ってseekに渡す
- seekはフレーム数で打ち切っていたところをファイルの位置(を超えるところ)まで読み込むように変更
- 使い方は
- 読み込んだあと、next_posから次の開始位置を確認する(あとでサンプルコード追加する)
- 補間の有無切り替え
- 初期化時に引数interpolateを受け取ってseekに渡す
- 1フレームごとにparseする関数を追加
- Jupyter, ipythonで使えるようにした
計測方法
array_status でID(PhyPath)を確認後、下記コマンドでとりあえずCSIの取得は可能(ちょっと不安定?)
PicoScenes "-d debug -i <ID> --mode logger --plot"
リアルタイムにプロットされ、終了後ファイルも生成される。
終了は手動でctrl-c
オプション
options:
-i <ID>
--mode injector
--repeat <Num Packets>
--delay <inter-packer delay> [microsec]
--txcm/rxcm <Specify antenna to use, binary representation (3 means both)>
--sts <Num space-time streams> : use 2 stream for 2x2 MIMO
モニターモードにすると、おそらく指定帯域のフレーム全部取得している?
公式ドキュメントでは、TXをinjectorモード、RXをモニターモードにする方法が紹介されている。
RXを array_prepare_for_picoscenes <PHYPath ID> <CHANNEL_CONFIG> でモニターモードに移行
- モニターモードから通常に戻す方法は不明。OS再起動したら元に戻る。
- DFSが怪しい 160MHz使う時は注意。
- 手持ちのPCでは5.9GHz帯を指定するとエラーが出て使えなかった
- https://www.intel.com/content/dam/support/us/en/documents/wireless/ax211-regulatory-webflyer-ccg.pdf を見る限り、FWアップデートで使えるようになるかも
array_prepare_for_picoscenes <ID> "<PrimaryFreq> <BandWidth MHz> <CenterFreq>"
array_prepare_for_picoscenes <PHYPath ID> "5180 160 5250" # 36ch-64ch
array_prepare_for_picoscenes <PHYPath ID> "5260 80 5290" # 52-64ch
array_prepare_for_picoscenes <PHYPath ID> "5500 160 5570" # 100-128ch
array_prepare_for_picoscenes <PHYPath ID> "5955 160 6025" # 5.9GHz
- チャネルについて補足
- W52, W53, W56がWiFi 5GHzのチャンネル
- W52は屋外利用に条件と許可が必要
W53は屋内限定(DFS必要)
W56は屋外利用可能(DFS必要) - W52: 5180, 5200, 5220, 5240 (36ch, 40ch, 44ch, 48ch)
- W53: 5260, 5280, 5300, 5320 (52ch, 56ch, 60ch, 64ch)
- W56: 5500, 5520, 5540, 5560, 5580, 5600, 5620, 5640, 5660, 5680, 5700
(100ch, 104ch, 108ch, 112ch, 116ch, 120ch, 124ch, 128ch, 132ch, 136ch, 140ch)
TXも同様にmonitorモードにしてからinjection用コマンドを実行するとフレームを送信できるようになる。
- 送信側を複数ストリーム(MIMO)にするためには送信アンテナとストリーム数を指定する必要あり。受信アンテナはデフォルトで全部使う(ドキュメント6.3.4)
- RX側では他のフレームも取得することになるが、受信側のコマンドでフィルタ可能
--source-address-filter <MACアドレス> - 送信間隔は1msでは受信側のタイムスタンプ見る限り処理が間に合ってなさそう。5msはパケットドロップあるが、遅れてはいない。
- モニターモードであれば複数TXからのCSI取得できそう。
injection用コマンド
- TX:
PicoScenes "-d debug -i 1 --mode injector --preset TX_CBW_160_HESU --repeat 1000 --delay 5e3 --txcm 3 --sts 2" - RX(通常とmonitorモード共通):
PicoScenes "-d debug -i 1 --mode logger --plot"
CSIデータ
- CSIの複素数データCSI、大きさMag、位相Phaseが格納されている
- 160MHzのinjectionだとサブキャリア数が2025になる
- 2048から23少ない。サブキャリアindexはどこかが飛んでいるわけではなく[-1012,1012]とされている
- サブキャリア-768, 768のところにパイロット信号っぽい落ち込みがあるが0ではない。ノイズを拾っているのか、LPFのようなものを使ったりしているのかは不明
- Mag, Phともに単位がわからない
- MagはCSIからnp.absしたものとだいたい一致
- Phは何かしらの補正をして直線になるようにしているものと思われる
- Phを[-π,π]に収まるように変換するとnp.angle(CSI)と一致することを確認
- →CSIから上記の逆変換をしている=計算した位相が連続になるように2πを加算か減算
PythonToolbox用のコード
from picoscenes import Picoscenes
import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import interactive, IntSlider # jupyter上でインタラクティブなグラフ表示
# dictデータのkey-valueを階層的に可視化
def dict_tree(d, layer):
if type(d) != dict:
if type(d) == list:
print(f'[{str(type(d))}] [{str(type(d[0]))}] * {len(d)}')
else:
print(f'[{str(type(d))}] {str(d)}')
return
else:
print()
for k,v in d.items():
print(f'{" " * layer}{k}:', end='')
dict_tree(v, layer + 1)
# ファイル読み込み
frames = Picoscenes("<ファイル名>")
# injectionされたフレームのみ抜き出し
picoscenes_frames = [
frame for frame in frames.raw
if 'PicoScenesHeader' in frame.keys()
]
# frameのdictデータ表示
dict_tree(frames.raw[0], 0)
dict_tree(picoscenes_frames[0], 0)
# i番目のフレームの表示
def csi_plot(i=0):
csi = picoscenes_frames[i]['CSI']
time_micro = picoscenes_frames[i]['RxSBasic']['timestamp']
subcarrier_idx = csi['SubcarrierIndex']
mag = np.array(csi['Mag'])
# ph = np.array(csi['Phase'])
# csi_cplx = np.array(csi['CSI'])
num_tx = csi['numTx']
num_rx = csi['numRx']
num_tones = csi['numTones']
num_pair = num_tx * num_rx
fig, ax = plt.subplots(1, 1, figsize=(6, 4))
for j in range(num_pair):
ax.plot(
subcarrier_idx,
mag[j*num_tones:(j+1)*num_tones],
label=f'time[ms] {time_micro//1000}, pair {j}')
ax.set_ylim(-5, 400)
ax.legend()
plt.show()
# jupyter上のインタラクティブな可視化
slider = IntSlider(value=0, min=0, max=len(picoscenes_frames)-1, step=1, description='frame index:')
w = interactive(csi_plot, i=slider)
w