學會使用 GNU Radio 中的 ZMQ,是從低階使用者向高階邁進的第一步!
因為學會了 ZMQ,就可以將 GNU Radio 中的實時資料流通過 socket 引到外面的 python/c 等大型應用程式中,做資料分析、展示、人工智慧等。
來自 ZeroMQ 官方介紹:ZeroMQ (0MQ, ZMQ),看起來像是一個可嵌入的網路庫,同時起到了並行框架的作用。它為您提供了在程序內、程序間、TCP和多播等各種傳輸中承載原子訊息的 socket 。
SINK | SOURCE | 特徵 |
---|---|---|
PUB | SUB | 廣播,可一對多 |
PUSH | PULL | 點播,對等對等網路 |
REQ | REP | 對等鏈路,一個請求一個回覆,類似使用者端伺服器 |
Data Blocks:
ZMQ data blocks 傳輸原始流資料;沒有格式化。資料型別和取樣率由饋送 ZMQSink 的流程圖確定。因此,接收資料的流程圖或程式必須知道這些引數,以便正確地解釋資料。
Message Blocks
不像普通的 ZeroMQ 字串,GNU Radio ZMQ Message Blocks 使用 PMT 對資料進行編碼和解碼。
zmq_tcp
和 zmq_ipc
。Virtual_Source
和 Virtual_Sink
塊的效率要高得多。TCP Bind vs Connect
一些使用者可能會想直接連線到GNU Radio ZMQ Blocks。雖然這是可能的,但需要謹慎。
首先要注意,在任何拓撲中,必須有一個到給定端點的繫結,而可能有多個到同一端點的連線。(A-B 之間繫結一次,可能會出現多個連線)
在 GNU Radio 中,stream sinks bind and stream sources connect。Message blocks 取決於引數設定。
還要注意:TCP端點的語意在繫結和連線之間有所不同。
TCP Bind
當繫結一個 TCP 端點時,您可以指定要偵聽的連線點。
如果您指定了一個IP地址,則說明 socket 只接受與該地址相關聯的網路上的連線(例如:127.0.0.1
or 192.168.1.123
)
在某些情況下,您可能希望在連線到節點的所有網路上進行偵聽。對於 GNU Radio,您應該使用 0.0.0.0
作為萬用字元地址;儘管 ZMQ 接受 * 作為萬用字元,但它並不是在所有情況下都能很好地工作。因此,您可以選擇繫結到 tcp://0.0.0.0:54321
請注意,如果您沒有輸入IP地址,bind 會將該值視為網路介面卡名稱(例如 eth0)。詳細資訊參閱:zmq_tcp
TCP Connect
連線 TCP 端點時,您可以指定要連線的遠端端點。您可以指定 IP 地址或 DNS 可解析名稱。
Wire Format
ZMQ stream blocks 具有傳遞標記的選項。此外,PUB/SUB塊支援過濾。這兩個選項都會影響 ZMQ-wire 協定。
當過濾器字串被提供給 PUB/SUB 塊時,GNU Radio 使用多部分訊息來傳送過濾器字串,然後是有效載荷。嘗試與 GNU Radio ZMQ 塊介面的非 GNU 無線電程式碼必須為此部分準備好,並將其丟棄。請注意,只有在指定了非空篩選器的情況下,傳送方才會傳送此訊息部分。
接下來,如果啟用了傳送標籤,則要傳送的資料視窗內的任何標籤都將以特殊格式編碼,並在有效載荷資料之前進行預處理。如果未啟用標記,則會忽略此檔頭。
這兩個特徵使得傳送器設定與接收器設定的匹配變得至關重要。否則將導致流程圖中出現執行時錯誤。
在同一個電腦上時,localhost 的 IP 地址應為 127.0.0.1
。它的開銷比完整的IP要小。
下面使用 PUB/SUB 的流程圖來自 Simulation_example:_AM_transmitter_and_receiver:
這個流程圖我也在 B 站上面有詳細的視訊介紹:GNU Radio 系列教學(十二)-- 窄頻 FM 收發系統(基於ZMQ模擬射頻傳送)
在不同電腦上時,則必須在該連線的每一端指定接收塊(sink block) 的 IP 和埠號。例如,如果 Sink 位於 IP 192.168.1.194:50241
,Source 位於 IP 192.168.1.85
,則 Source 和 Sink 塊都必須指定 Sink IP 和埠 192.168.1.194:00241
。
下面的 Python 程式在其 REQ socket 上接收字串訊息,將文字變成大寫,然後在其 REP socket 上傳送出去。術語在這裡變得混亂,因為傳入的 REQ 來自 GR ZMQ_REP_Message_Sink
,並返回到 ZMQ_REQ_Message_Source
。
只需記住: sink 是流程圖的終點,source 是流程圖的起點。
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# zmq_REQ_REP_server.py
# This server program capitalizes received strings and returns them.
# NOTES:
# 1) To comply with the GNU Radio view, messages are received on the REQ socket and sent on the REP socket.
# 2) The REQ and REP messages must be on separate port numbers.
import pmt
import zmq
_debug = 0 # set to zero to turn off diagnostics
# create a REQ socket
_PROTOCOL = "tcp://"
_SERVER = "127.0.0.1" # localhost
_REQ_PORT = ":50246"
_REQ_ADDR = _PROTOCOL + _SERVER + _REQ_PORT
if (_debug):
print ("'zmq_REQ_REP_server' version 20056.1 connecting to:", _REQ_ADDR)
req_context = zmq.Context()
if (_debug):
assert (req_context)
req_sock = req_context.socket (zmq.REQ)
if (_debug):
assert (req_sock)
rc = req_sock.connect (_REQ_ADDR)
if (_debug):
assert (rc == None)
# create a REP socket
_PROTOCOL = "tcp://"
_SERVER = "127.0.0.1" # localhost
_REP_PORT = ":50247"
_REP_ADDR = _PROTOCOL + _SERVER + _REP_PORT
if (_debug):
print ("'zmq_REQ_REP_server' version 20056.1 binding to:", _REP_ADDR)
rep_context = zmq.Context()
if (_debug):
assert (rep_context)
rep_sock = rep_context.socket (zmq.REP)
if (_debug):
assert (rep_sock)
rc = rep_sock.bind (_REP_ADDR)
if (_debug):
assert (rc == None)
while True:
# Wait for next request from client
data = req_sock.recv()
message = pmt.to_python(pmt.deserialize_str(data))
print("Received request: %s" % message)
output = message.upper()
# Send reply back to client
rep_sock.send (pmt.serialize_str(pmt.to_pmt(output)))
安裝 NetCat:方便我們測試 TCP
-《NetCat使用指南》
-《Sending TCP/UDP packets using Netcat》
-《Simple client / server with nc not working》
注意,這鬼軟體有好幾個不同的軟體,我用的是 openbsd-netcat
sudo pacman -S openbsd-netcat
上面程式碼:
kind | port | method | func | C/S |
---|---|---|---|---|
REQ | 50246 | connect | recv() | server |
REP | 50247 | bind | send() | client |
while 迴圈中用 REQ 等待接收,然後轉為大寫,用 REP 傳送出去:(比較坑的是,我用 netcat 建立 tcp 伺服器和使用者端,無法與上面 python 指令碼通訊,似乎一啟動,建立連線,server 就異常退出了,最終還是得用 GNN Radio 開啟兩個 ZMQ 工程,然後與這個 python 指令碼通訊,整體資訊流如下:)
與上面 demo 類似,是基於 PUSH/PULL 傳遞訊息。
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# zmq_PUSH_PULL_server.py
import sys
import pmt
import zmq
_debug = 0 # set to zero to turn off diagnostics
# create a PUSH socket
_PROTOCOL = "tcp://"
_SERVER = "127.0.0.1" # localhost
_PUSH_PORT = ":50252"
_PUSH_ADDR = _PROTOCOL + _SERVER + _PUSH_PORT
if (_debug):
print ("'zmq_PUSH_PULL_server' version 20068.1 binding to:", _PUSH_ADDR)
push_context = zmq.Context()
if (_debug):
assert (push_context)
push_sock = push_context.socket (zmq.PUSH)
if (_debug):
assert (push_sock)
rc = push_sock.bind (_PUSH_ADDR)
if (_debug):
assert (rc == None)
# create a PULL socket
_PROTOCOL = "tcp://"
_SERVER = "127.0.0.1" # localhost
_PULL_PORT = ":50251"
_PULL_ADDR = _PROTOCOL + _SERVER + _PULL_PORT
if (_debug):
print ("'zmq_PUSH_PULL_server' connecting to:", _PULL_ADDR)
pull_context = zmq.Context()
if (_debug):
assert (pull_context)
pull_sock = pull_context.socket (zmq.PULL)
if (_debug):
assert (pull_sock)
rc = pull_sock.connect (_PULL_ADDR)
if (_debug):
assert (rc == None)
while True:
# Wait for next request from client
data = pull_sock.recv()
message = pmt.to_python(pmt.deserialize_str(data))
# print("Received request: %s" % message)
output = message.upper() # capitalize message
# Send reply back to client
push_sock.send (pmt.serialize_str(pmt.to_pmt(output)))
個 demo 是幾乎貫穿後面 GNU Radio 高階用法的最重要的 DEMO。 因為,通常情況下我們會使用 GNU Radio 進行訊號處理,但希望資料流流入普通 python 程式,然後做豐富的資料分析等邏輯。這裡,PUB 和 PUSH 可以讓應用程式獲得這些資料流。(這裡我們將 127.0.0.1
換成了 *
,這樣能夠讓同一區域網內的裝置都能存取)
一般的,流程圖中採用 PUB/PUSH Sink,將資料送出:
然後,普通 python 指令碼就可以對其進行 recv:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# zmq_SUB_proc.py
# Author: Marc Lichtman
import zmq
import numpy as np
import time
import matplotlib.pyplot as plt
context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.connect("tcp://127.0.0.1:55555") # connect, not bind, the PUB will bind, only 1 can bind
socket.setsockopt(zmq.SUBSCRIBE, b'') # subscribe to topic of all (needed or else it won't work)
while True:
if socket.poll(10) != 0: # check if there is a message on the socket
msg = socket.recv() # grab the message
print(len(msg)) # size of msg
data = np.frombuffer(msg, dtype=np.complex64, count=-1) # make sure to use correct data type (complex64 or float32); '-1' means read all data in the buffer
print(data[0:10])
# plt.plot(np.real(data))
# plt.plot(np.imag(data))
# plt.show()
else:
time.sleep(0.1) # wait 100ms and try again
[1]. GNU Radio 系列教學(一) —— 什麼是 GNU Radio
[2]. GNU Radio 系列教學(二) —— 繪製第一個訊號分析流程圖
[3]. GNU Radio 系列教學(三) —— 變數的使用
[4]. GNU Radio 系列教學(四) —— 位元的打包與解包
[5]. GNU Radio 系列教學(五) —— 流和向量
[6]. GNU Radio 系列教學(六) —— 基於層建立自己的塊
[7]. GNU Radio 系列教學(七)—— 建立第一個塊
[8]. GNU Radio 系列教學(八)—— 建立能處理向量的 Python 塊
[9]. GNU Radio 系列教學(九)—— Python 塊的訊息傳遞
[10]. GNU Radio 系列教學(十)—— Python 塊的 Tags
[11]. GNU Radio 系列教學(十一)—— 低通濾波器
[12]. GNU Radio 系列教學(十二)—— 窄頻 FM 收發系統(基於ZMQ模擬射頻傳送)
[13]. GNU Radio 系列教學(十三)—— 用兩個 HackRF 實現 FM 收發
[14]. SDR 教學實戰 —— 利用 GNU Radio + HackRF 做 FM 收音機
[15]. SDR 教學實戰 —— 利用 GNU Radio + HackRF 做藍芽定頻測試工具(超低成本)
: 如果覺得不錯,幫忙點個支援哈~