nihsok が 2023年11月03日19時39分56秒 に編集
初版
タイトルの変更
SDRで受信した短波FAXを電子ペーパーに表示
タグの変更
SDR
短波
FAX
電子ペーパー
無線
RaspberryPiPicoW
FFT
Python
framebuffer
HAT
メイン画像の変更
記事種類の変更
セットアップや使用方法
ライセンスの変更
(GPL-3.0+) GNU General Public License, version 3
本文の変更
本記事は2部構成からなる。 1. SDRで受信した短波FAXをデコードして画像を取得 2. Raspberry Pi Pico Wで画像をアップロードして電子ペーパーで表示 今回は気象庁の気象模写無線通報を対象とする。(https://www.jma.go.jp/jmh/jmhmenu.html ) ![概念図](https://camo.elchika.com/38c24140c13836c22ce4b6d6f748d6eab0e1ef8d/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f66336238363735322d663630612d343866342d613963622d3331306133646133386665322f35363462396462362d653362622d343833312d383239392d333838666531393561663138/) # 1. SDRで受信した短波FAXをデコードして画像を取得 短波をSDRドングルで受信して復調した音声をもとに、白~黒を割り当てて画像に変換する。 ## 材料 - SDR - アンテナ 今回はRTL-SDR.com公式のアンテナとのセット(https://ja.aliexpress.com/item/32936334224.html?gatewayAdapt=glo2jpn)を購入。7000円程度 - Raspberry Pi 3A+ ハードの制限はあまりない。USBポートがありLANに繋がってOS(とPython)が動けばOK。スペックが高い方が画像がすぐできる。 ## 受信した信号から画像のデコード IOC576という規格に従う。特徴としては - USB (upper sideband) で変調されている。放送周波数の1.9kHz下でUSB受信したときに、1.5kHz~2.3kHzが白~黒に対応する。 - 1分間に120行の画像が送信される(=0.5秒で改行) この仕様に従い、まずはUSBで復調する。 ```bash:usb.sh timeout -k 20 1140 /usr/local/bin/rtl_fm -d 2 -f 7793.1k -M usb -s 23040 -g 49.6 -E dc -E direct2 | sox -t raw -e signed -c 1 -b 16 -r 23040 - /tmp/fax.wav ``` とりあえず20分弱受信している。rtl_fmはサンプリングレートを指定できるので、576の倍数Hzにすると後のパラメータのきりがよくなる。 次に音声のFFTにより最大振幅となる周波数を求め、白~黒に割り当てる。 ```python:fax.py import numpy as np import wave from PIL import Image #0=0Hz,1=288Hz,4=1152Hz,5=1440Hz,8=2304Hz,20=5760Hz,40=11520Hz slide = 8 window = 80 lbound = 5 ubound = 9 factor = 80 taper = np.hanning(window) with wave.open('/tmp/fax.wav','r') as w: data = np.frombuffer(w.readframes(-1),dtype='int16') ary = np.array([ np.argmax(np.abs( np.fft.rfft( taper*data[i:i+window] )[lbound:ubound] )) for i in range(0,len(data)-window,slide) ]) width = int(2*5760/slide) ary = np.append( np.minimum(ary*factor,255) , [255]*( width - len(ary) % width ) ).reshape([-1,width]) ary = np.roll(ary,-np.argmin(np.sum(ary,axis=0)),axis=1) Image.fromarray(ary.astype(np.uint8)).save('tmp.png') ``` 8点ずつスライドして1度に80点をFFTしている。この場合波数5~9が白~黒に対応(=5段階のグレースケール)。グレーの諧調を増やすためには周波数解像度を上げる必要があるが、そのためにはより長い時間幅についてFFTを行う必要があり、時間解像度が犠牲になる(不確定性原理)。0埋めしてからFFTすることで周波数解像度のみ上げることも考えられるが、計算時間が余計にかかってしまう。 現在は端から順番に計算しているため20分の音声データを変換するのに10分程度かかっているが、Numpy.lib.stride_tricks.sliding_window_viewで並列に計算すれば時間を短縮できるかも? 秒単位の制御では画像の開始をとらえることができない。IOC576の仕様では冒頭に同期のための位相信号が送信されるが、今回の手法ではノイズが多くあまりあてにならないので、ポストプロセスとして一番黒い部分を画像の端にシフトすることで解決している。 # 2. Raspberry Pi Pico Wに画像をアップロードして電子ペーパーで表示 市販のRaspberry Pi Pico HATを使い、電子ペーパーに画像を表示させる。また、オンライン(LAN経由)で画像をアップロードできるようにする。 ## 材料 - Raspberry Pi Pico W 秋月電子で1200円(税込) - 800×480, 7.5inch E-Ink display HAT for Raspberry Pi (https://www.waveshare.com/product/raspberry-pi/displays/e-paper/7.5inch-e-paper-hat.htm) $56.99+送料$5=計9000円程度 ## ハードウェアの準備 Pico Wにピンをつけて、ソケットに差し込む ==HATの表側はピンを指すためのソケット、裏側はピンがとびでている構造で、表側に設置するようにと書かれている。はじめ手を抜いて裏側に設置しようとしたが、基板上のスイッチとPico WのMicroUSBポートとが干渉して設置できなかった。手許にPi 3A+から抜き取ったピンがあったので流用した。ピンは全部接続する必要はなく、11か所(VSYS, GND, RUN, GP2,3,8,9,10,11,12,13)で十分。== ## ソフトウェアの準備 ドキュメントに従って進める。 https://www.waveshare.com/wiki/Pico-ePaper-2.9#Hardware_Connection (2.9インチ用だが、基本的には同じはず) Pico WはMicroPythonを動かすための初期設定を行う(省略) 以下の公式サンプルコードをコピペして実行すると、デモ用の画像が描画される。 https://github.com/waveshareteam/Pico_ePaper_Code ### 画像の準備 任意の画像を表示するためには、画像のサイズのframebufferに0(黒)1(白)を代入して与える。Image2Lcdという便利なソフトがあるらしいが、簡単に実装してみたのが以下のコード。 ```python:img2grayfb.py from PIL import Image import numpy as np import sys inv_flag = True #True fo wb, False for rwb width = 800 height = 480 rotate = 0 gray = Image.open(sys.argv[1]).convert('LA') width_org, height_org = gray.size aspect_org = width_org/height_org if aspect_org < 1 : gray = gray.rotate(90,expand=True) width_org, height_org = height_org, width_org aspect_org = width_org/height_org # resize, monochrome(0/1), padding, and return narray if aspect_org > width/height: #high aspect -> adjust width & pad height height_tmp = round(width/aspect_org) img2d = np.vstack(( np.array(gray.resize((width,height_tmp)).convert('1')), np.ones((height-height_tmp,width),dtype=np.uint8) )) else: #low aspect -> adjust height & pad width width_tmp = round(height*aspect_org) img2d = np.hstack(( np.array(gray.resize((width_tmp,height)).convert('1')), np.ones((height,width-width_tmp),dtype=np.uint8) )) if inv_flag: img2d = 1-img2d shift=np.uint8(range(0,8)[::-1]) with open('tmp.buf','wb') as f: for j in range(0,height): f.write(np.array( [np.left_shift( img2d[j,i:i+8],shift ).sum() for i in range(0,width,8)], dtype=np.uint8 )) ``` 画像がなるべく大きく表示されるように回転して、横長の画像は横幅に合わせ、縦長の画像は縦幅に合わせている。 最初はグレーも対応するつもりだったので少し複雑になっている。 ### 画像のアップロード処理 公式サンプルコードのmainの部分のみ少し書き加えたコードをRaspberry Pi Pico Wで動かす。 ```python:main.py if __name__=='__main__': epd = EPD_7in5() epd.Clear() ip = wifi.connect('ssid','password') print(ip[0]) epd.text(ip[0],5,10,0xff) epd.display(epd.buffer) s = usocket.socket() s.setsockopt(usocket.SOL_SOCKET,usocket.SO_REUSEADDR,1) s.bind(('0.0.0.0',80)) s.listen(1) epd.delay_ms(2000) epd.sleep() while True: cl, addr = s.accept() data = b'' while not b'\r\n\r\n' in data: data += cl.recv(1024) print(data) with open('tmp.buf','wb') as f: f.write(data[data.find(b'\r\n\r\n')+4:]) while True: data = cl.recv(4096) if not data: break f.write(data) cl.close() epd.init() with open('tmp.buf','rb') as f: for i in range(48): epd.buffer[i*1000:(i+1)*1000]=f.read(1000) epd.display(epd.buffer) epd.delay_ms(2000) epd.sleep() ``` ```python:wifi.py import network import utime def connect(ssid,passwd,retry=10): wlan = network.WLAN(network.STA_IF) wlan.active(True) wlan.connect(ssid,passwd) utime.sleep(4) return wlan.ifconfig() ``` 電源をつないだときにはIPアドレスが表示される。 あとはそのIPアドレスに先ほど作成したframebufferを送るたびに画像が書き換えられる。 ```bash:send.sh timeout 3 curl -X POST -H "Content-Type: application/octet-stream" --data-binary @tmp.buf http://192.168.10.101 ``` ## 結果 ![AWPN](https://camo.elchika.com/45671774655aedeed8e7bed2131b7527fadf0309/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f66336238363735322d663630612d343866342d613963622d3331306133646133386665322f37386636303761382d376261332d343235632d613139372d663937393931346535313563/) ![FXFE572/FXFE82](https://camo.elchika.com/4e4eaa1e5b0c4afd08c0aa48703d8159c5f12f55/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f66336238363735322d663630612d343866342d613963622d3331306133646133386665322f63633837383039302d343530662d343136302d613565362d323633356165663364316534/) # まとめ SDRで短波FAXを受信しで画像に変換し、電子ペーパーで表示した。 画像が結構ノイジーになる行がある。大丈夫なときははっきり見えているのでハード面、特にアンテナが短波用ではないのが原因と考えられる。 (現時点で、雨が降っているとノイズが増えることは確認済み) FFT部分のパラメータはもう少し調整してもよいかもしれない。 ## 今後の課題 - FFTを並列にして高速化 - スケジュール化 - 電子ペーパーディスプレイに枠をつける - 短波用アンテナを試す