mirror of
https://gitlab.eurecom.fr/oai/openairinterface5g.git
synced 2026-07-13 04:30:28 +00:00
ci: simulate freq drift and verify freq correction
A new test case 5g_zmq_radio_trs is created by copying the folder 5g_zmq_radio_1x1. A gnuradio python script (rotate_zmq.py) generated by grc is used to read the samples from gNB and apply a freq drift and send them to a ZMQ sink. The UE reads samples from the ZMQ port on gnuradio. The gnuradio script is modified to receive message containing freq offsets from another script (send_freq_offset_zmq.py). Dockerfile.gnuradio builds an ubuntu 24.04 image and copy both python scripts. Modified the docker-compose.yaml to include a new service gnuradio-zmq that starts by running rotate_zmq.py. Created container_5g_zmq_ocudu_trs.xml by copying container_5g_zmq_ocudu_1x1.xml. Included a Custom_Command to run send_freq_offset_zmq.py and another custom_command to grep the UE stdout for TRS freq correction. Added a PUSH socket in zmq_radio.cpp to send carrier frequency to gnuradio and remove the offset applied so that the test can continue without a freq offset. Signed-off-by: Sakthivel Velumani <s.velumani@northeastern.edu>
This commit is contained in:
123
ci-scripts/xml_files/container_5g_zmq_ocudu_trs.xml
Normal file
123
ci-scripts/xml_files/container_5g_zmq_ocudu_trs.xml
Normal file
@@ -0,0 +1,123 @@
|
||||
<!-- SPDX-License-Identifier: LicenseRef-CSSL-1.0 -->
|
||||
|
||||
<testCaseList>
|
||||
<htmlTabRef>ocudu-5gnr-tdd-trs</htmlTabRef>
|
||||
<htmlTabName>OCUDU gnb split8 with OAI UE</htmlTabName>
|
||||
<htmlTabIcon>wrench</htmlTabIcon>
|
||||
|
||||
<testCase>
|
||||
<class>Pull_Local_Registry</class>
|
||||
<desc>Pull Images from Local Registry</desc>
|
||||
<node>localhost</node>
|
||||
<images>oai-gnb-asan oai-nr-ue-asan</images>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Create_Workspace</class>
|
||||
<desc>Create new Workspace</desc>
|
||||
<node>localhost</node>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Deploy_Object</class>
|
||||
<desc>Deploy OAI 5G CoreNetwork</desc>
|
||||
<node>localhost</node>
|
||||
<yaml_path>ci-scripts/yaml_files/5g_zmq_radio_trs</yaml_path>
|
||||
<services>mysql oai-amf oai-smf oai-upf oai-ext-dn</services>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Deploy_Object</class>
|
||||
<desc>Deploy OCUDU Split 8 gNB + GNURadio + OAI UE</desc>
|
||||
<node>localhost</node>
|
||||
<yaml_path>ci-scripts/yaml_files/5g_zmq_radio_trs</yaml_path>
|
||||
<services>ocudu-gnb gnuradio-zmq oai-nr-ue</services>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Attach_UE</class>
|
||||
<desc>Attach OAI UE (Wait for IP)</desc>
|
||||
<id>rfsim5g_ue</id>
|
||||
<node>localhost</node>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Custom_Command</class>
|
||||
<desc>Simulate frequency drift of 50 Hz per second for 13 seconds</desc>
|
||||
<node>localhost</node>
|
||||
<command>docker exec gnuradio-zmq python3 /app/send_freq_offset_zmq.py --num-msg 13</command>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Custom_Command</class>
|
||||
<desc>Verify TRS frequency correction</desc>
|
||||
<node>localhost</node>
|
||||
<command>timeout 3 docker logs -f oai-nr-ue 2>&1 | grep -m1 'CFO estimated \([5-6][0-9][0-9]\) from TRS exceeded threshold'</command>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Ping</class>
|
||||
<desc>Ping ext-dn from NR-UE</desc>
|
||||
<id>rfsim5g_ue</id>
|
||||
<node>localhost</node>
|
||||
<svr_id>rfsim5g_ext_dn</svr_id>
|
||||
<svr_node>localhost</svr_node>
|
||||
<ping_args>-c 20 -i 0.25</ping_args>
|
||||
<ping_packetloss_threshold>5</ping_packetloss_threshold>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Ping</class>
|
||||
<desc>Ping NR-UE from ext-dn</desc>
|
||||
<id>rfsim5g_ext_dn</id>
|
||||
<node>localhost</node>
|
||||
<svr_id>rfsim5g_ue</svr_id>
|
||||
<svr_node>localhost</svr_node>
|
||||
<ping_args>-c 20 -i 0.25</ping_args>
|
||||
<ping_packetloss_threshold>5</ping_packetloss_threshold>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Iperf</class>
|
||||
<desc>Iperf (DL/3Mbps/UDP)(20 sec)</desc>
|
||||
<iperf_args>-u -b 3M -t 20 -R</iperf_args>
|
||||
<id>rfsim5g_ue</id>
|
||||
<node>localhost</node>
|
||||
<svr_id>rfsim5g_ext_dn</svr_id>
|
||||
<svr_node>localhost</svr_node>
|
||||
<iperf_packetloss_threshold>5</iperf_packetloss_threshold>
|
||||
<iperf_bitrate_threshold>90</iperf_bitrate_threshold>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Iperf</class>
|
||||
<desc>Iperf (UL/3Mbps/UDP)(20 sec)</desc>
|
||||
<iperf_args>-u -b 3M -t 20</iperf_args>
|
||||
<id>rfsim5g_ue</id>
|
||||
<node>localhost</node>
|
||||
<svr_id>rfsim5g_ext_dn</svr_id>
|
||||
<svr_node>localhost</svr_node>
|
||||
<iperf_packetloss_threshold>5</iperf_packetloss_threshold>
|
||||
<iperf_bitrate_threshold>90</iperf_bitrate_threshold>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Undeploy_Object</class>
|
||||
<always_exec>true</always_exec>
|
||||
<desc>Undeploy all OAI 5G stack</desc>
|
||||
<node>localhost</node>
|
||||
<yaml_path>ci-scripts/yaml_files/5g_zmq_radio_trs</yaml_path>
|
||||
<analysis>
|
||||
<services>oai-nr-ue</services>
|
||||
</analysis>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Clean_Test_Server_Images</class>
|
||||
<always_exec>true</always_exec>
|
||||
<desc>Clean Test Images on Test Server</desc>
|
||||
<node>localhost</node>
|
||||
<images>oai-gnb-asan oai-nr-ue-asan</images>
|
||||
</testCase>
|
||||
|
||||
</testCaseList>
|
||||
11
ci-scripts/yaml_files/5g_zmq_radio_trs/Dockerfile.gnuradio
Normal file
11
ci-scripts/yaml_files/5g_zmq_radio_trs/Dockerfile.gnuradio
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM ubuntu:24.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gnuradio \
|
||||
python3-zmq \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
COPY rotate_zmq.py send_freq_offset_zmq.py .
|
||||
178
ci-scripts/yaml_files/5g_zmq_radio_trs/docker-compose.yaml
Normal file
178
ci-scripts/yaml_files/5g_zmq_radio_trs/docker-compose.yaml
Normal file
@@ -0,0 +1,178 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
services:
|
||||
mysql:
|
||||
container_name: "rfsim5g-mysql"
|
||||
image: mysql:9.6
|
||||
init: true
|
||||
volumes:
|
||||
- ../5g_rfsimulator/oai_db.sql:/docker-entrypoint-initdb.d/oai_db.sql
|
||||
- ../5g_rfsimulator/mysql-healthcheck.sh:/tmp/mysql-healthcheck.sh
|
||||
environment:
|
||||
- TZ=Europe/Paris
|
||||
- MYSQL_DATABASE=oai_db
|
||||
- MYSQL_USER=test
|
||||
- MYSQL_PASSWORD=test
|
||||
- MYSQL_ROOT_PASSWORD=linux
|
||||
healthcheck:
|
||||
test: /bin/bash -c "/tmp/mysql-healthcheck.sh"
|
||||
start_period: 10s
|
||||
start_interval: 500ms
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.131
|
||||
oai-amf:
|
||||
container_name: "rfsim5g-oai-amf"
|
||||
image: oaisoftwarealliance/oai-amf:v2.2.1
|
||||
environment:
|
||||
- TZ=Europe/paris
|
||||
volumes:
|
||||
- ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-amf/etc/config.yaml
|
||||
depends_on:
|
||||
- mysql
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.132
|
||||
oai-smf:
|
||||
container_name: "rfsim5g-oai-smf"
|
||||
image: oaisoftwarealliance/oai-smf:v2.2.1
|
||||
environment:
|
||||
- TZ=Europe/Paris
|
||||
volumes:
|
||||
- ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-smf/etc/config.yaml
|
||||
depends_on:
|
||||
- oai-amf
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.133
|
||||
oai-upf:
|
||||
container_name: "rfsim5g-oai-upf"
|
||||
image: oaisoftwarealliance/oai-upf:v2.2.1
|
||||
init: true
|
||||
environment:
|
||||
- TZ=Europe/Paris
|
||||
volumes:
|
||||
- ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-upf/etc/config.yaml
|
||||
depends_on:
|
||||
- oai-smf
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_ADMIN
|
||||
cap_drop:
|
||||
- ALL
|
||||
privileged: true
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.134
|
||||
interface_name: eth0
|
||||
traffic_net:
|
||||
ipv4_address: 192.168.72.134
|
||||
interface_name: eth1
|
||||
oai-ext-dn:
|
||||
privileged: true
|
||||
container_name: rfsim5g-oai-ext-dn
|
||||
image: oaisoftwarealliance/trf-gen-cn5g:latest
|
||||
init: true
|
||||
entrypoint: /bin/bash -c \
|
||||
"iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;"\
|
||||
"ip route add 12.1.1.0/24 via 192.168.72.134 dev eth0; sleep infinity"
|
||||
depends_on:
|
||||
- oai-upf
|
||||
networks:
|
||||
traffic_net:
|
||||
ipv4_address: 192.168.72.135
|
||||
healthcheck:
|
||||
test: /bin/bash -c "ping -c 2 192.168.72.134"
|
||||
start_period: 10s
|
||||
start_interval: 500ms
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
ocudu-gnb:
|
||||
image: ghcr.io/bpodrygajlo/ocudu/ocudu-avx2:7cecca5
|
||||
container_name: ocudu-gnb
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- SYS_NICE
|
||||
depends_on:
|
||||
- oai-ext-dn
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.140
|
||||
volumes:
|
||||
- ./ocudu.yml:/ocudu.yml
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep gnb_split_8"
|
||||
start_period: 10s
|
||||
start_interval: 500ms
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
command: ./usr/local/bin/gnb_split_8 -c /ocudu.yml
|
||||
|
||||
gnuradio-zmq:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.gnuradio
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- SYS_NICE
|
||||
depends_on:
|
||||
- ocudu-gnb
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.141
|
||||
command:
|
||||
bash -c "cd /app/ & python3 rotate_zmq.py --zmq-req-src tcp://192.168.71.140:4558 --zmq-req-snk tcp://0.0.0.0:4559"
|
||||
|
||||
oai-nr-ue:
|
||||
image: ${REGISTRY-oaisoftwarealliance/}${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
container_name: rfsim5g-oai-nr-ue
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_ADMIN # for interface bringup
|
||||
- NET_RAW # for ping
|
||||
- SYS_NICE
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -r 51 --numerology 1 --band 78 -C 3489420000 --ssb 0 -E --uicc0.imsi 208990100001100 --uecap_file /opt/oai-nr-ue/etc/uecap.xml --log_config.global_log_options level,nocolor,time --device.name oai_zmqdevif --zmq.[0].tx_channels tcp://0.0.0.0:4556 --zmq.[0].rx_channels tcp://192.168.71.141:4559
|
||||
depends_on:
|
||||
- gnuradio-zmq
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.150
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
volumes:
|
||||
- ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/uecap_ports1.xml:/opt/oai-nr-ue/etc/uecap.xml
|
||||
- ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep nr-uesoftmodem"
|
||||
start_period: 10s
|
||||
start_interval: 500ms
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
networks:
|
||||
public_net:
|
||||
driver: bridge
|
||||
name: rfsim5g-oai-public-net
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 192.168.71.128/26
|
||||
driver_opts:
|
||||
com.docker.network.bridge.name: "rfsim5g-public"
|
||||
traffic_net:
|
||||
driver: bridge
|
||||
name: rfsim5g-oai-traffic-net
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 192.168.72.128/26
|
||||
driver_opts:
|
||||
com.docker.network.bridge.name: "rfsim5g-traffic"
|
||||
52
ci-scripts/yaml_files/5g_zmq_radio_trs/ocudu.yml
Normal file
52
ci-scripts/yaml_files/5g_zmq_radio_trs/ocudu.yml
Normal file
@@ -0,0 +1,52 @@
|
||||
# SPDX-FileCopyrightText: Copyright (C) 2021-2026 Software Radio Systems Limited
|
||||
# SPDX-License-Identifier: BSD-3-Clause-Open-MPI
|
||||
# Refer to https://gitlab.com/ocudu/ocudu/-/raw/dev/LICENSE?ref_type=heads
|
||||
|
||||
cu_cp:
|
||||
amf:
|
||||
addrs: 192.168.71.132
|
||||
port: 38412
|
||||
bind_addrs: 192.168.71.140
|
||||
supported_tracking_areas:
|
||||
- tac: 1
|
||||
plmn_list:
|
||||
- plmn: "20899"
|
||||
tai_slice_support_list:
|
||||
- sst: 1
|
||||
|
||||
ru_sdr:
|
||||
device_driver: zmq
|
||||
device_args: tx_port=tcp://0.0.0.0:4558,rx_port=tcp://192.168.71.150:4556
|
||||
srate: 23.04
|
||||
tx_gain: 25
|
||||
rx_gain: 25
|
||||
|
||||
cell_cfg:
|
||||
dl_arfcn: 632628
|
||||
band: 78
|
||||
channel_bandwidth_MHz: 20
|
||||
common_scs: 30
|
||||
plmn: "20899"
|
||||
tac: 1
|
||||
pci: 1
|
||||
tdd_ul_dl_cfg:
|
||||
dl_ul_tx_period: 5
|
||||
nof_dl_slots: 3
|
||||
nof_dl_symbols: 10
|
||||
nof_ul_slots: 1
|
||||
nof_ul_symbols: 2
|
||||
csi:
|
||||
csi_rs_enabled: false
|
||||
pucch:
|
||||
formats: f0_and_f2
|
||||
nof_cell_csi_res: 0
|
||||
|
||||
log:
|
||||
filename: gnb.log
|
||||
all_level: debug
|
||||
|
||||
pcap:
|
||||
mac_enable: false
|
||||
mac_filename: /tmp/gnb_mac.pcap
|
||||
ngap_enable: false
|
||||
ngap_filename: /tmp/gnb_ngap.pcap
|
||||
216
ci-scripts/yaml_files/5g_zmq_radio_trs/rotate_zmq.py
Normal file
216
ci-scripts/yaml_files/5g_zmq_radio_trs/rotate_zmq.py
Normal file
@@ -0,0 +1,216 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0
|
||||
#
|
||||
# GNU Radio Python Flow Graph
|
||||
# Title: Simple doppler injector
|
||||
# GNU Radio version: 3.10.9.2
|
||||
|
||||
import threading
|
||||
import struct
|
||||
import sys
|
||||
import signal
|
||||
from argparse import ArgumentParser
|
||||
|
||||
import zmq
|
||||
|
||||
from gnuradio import analog, blocks, gr
|
||||
from gnuradio.filter import firdes
|
||||
from gnuradio.fft import window
|
||||
from gnuradio import zeromq
|
||||
|
||||
|
||||
class untitled(gr.top_block):
|
||||
|
||||
def __init__(self, samp_rate=23040000, rx_freq=0, freq_off=0, zmq_req_src='tcp://127.0.0.1:4445', zmq_req_snk='tcp://127.0.0.1:4446', zmq_pull_addr='tcp://*:5555', zmq_pull_oai_addr='tcp://*5556'):
|
||||
gr.top_block.__init__(self, "Simple doppler injector", catch_exceptions=True)
|
||||
|
||||
##################################################
|
||||
# Variables
|
||||
##################################################
|
||||
self.samp_rate = samp_rate
|
||||
self.freq_off = freq_off
|
||||
self.rx_freq = rx_freq
|
||||
|
||||
##################################################
|
||||
# Blocks
|
||||
##################################################
|
||||
|
||||
self.zeromq_req_source_0 = zeromq.req_source(gr.sizeof_gr_complex, 1, zmq_req_src, 100, False, (-1), False)
|
||||
self.zeromq_rep_sink_0 = zeromq.rep_sink(gr.sizeof_gr_complex, 1, zmq_req_snk, 100, False, (-1), True)
|
||||
self.blocks_throttle2_0_0 = blocks.throttle(
|
||||
gr.sizeof_gr_complex*1, samp_rate, True,
|
||||
0 if "auto" == "auto" else max(
|
||||
int(float(0.1) * samp_rate) if "auto" == "time" else int(0.1), 1)
|
||||
)
|
||||
self.blocks_multiply_xx_0 = blocks.multiply_vcc(1)
|
||||
self.analog_sig_source_x_0_1_0 = analog.sig_source_c(samp_rate, analog.GR_COS_WAVE, freq_off, 1, 0, 0)
|
||||
|
||||
##################################################
|
||||
# Connections
|
||||
##################################################
|
||||
self.connect((self.analog_sig_source_x_0_1_0, 0), (self.blocks_multiply_xx_0, 0))
|
||||
self.connect((self.blocks_multiply_xx_0, 0), (self.zeromq_rep_sink_0, 0))
|
||||
self.connect((self.blocks_throttle2_0_0, 0), (self.blocks_multiply_xx_0, 1))
|
||||
self.connect((self.zeromq_req_source_0, 0), (self.blocks_throttle2_0_0, 0))
|
||||
|
||||
##################################################
|
||||
# ZMQ sockets
|
||||
##################################################
|
||||
self._zmq_ctx = zmq.Context()
|
||||
self._zmq_ctx_oai = zmq.Context()
|
||||
|
||||
# PULL - receive freq offset updates from Python script
|
||||
self._pull_sock = self._zmq_ctx.socket(zmq.PULL)
|
||||
self._pull_sock.setsockopt(zmq.LINGER, 0)
|
||||
self._pull_sock.bind(zmq_pull_addr)
|
||||
print(f"[zmq] PULL bound to {zmq_pull_addr}")
|
||||
|
||||
# PULL - receive carrier freq updates from OAI
|
||||
self._pull_sock_oai = self._zmq_ctx_oai.socket(zmq.PULL)
|
||||
self._pull_sock_oai.setsockopt(zmq.LINGER, 0)
|
||||
self._pull_sock_oai.bind(zmq_pull_oai_addr)
|
||||
print(f"[zmq] PULL bound to {zmq_pull_oai_addr}")
|
||||
|
||||
self._zmq_thread = threading.Thread(target=self._zmq_listener, daemon=True)
|
||||
self._zmq_thread_oai = threading.Thread(target=self._zmq_listener_oai, daemon=True)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Getters / setters
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def get_samp_rate(self):
|
||||
return self.samp_rate
|
||||
|
||||
def get_rx_freq(self):
|
||||
return self.rx_freq
|
||||
|
||||
def set_rx_freq(self, rx_freq):
|
||||
self.rx_freq = rx_freq
|
||||
print(f"[rx_freq] -> {self.rx_freq}")
|
||||
|
||||
def set_samp_rate(self, samp_rate):
|
||||
self.samp_rate = samp_rate
|
||||
self.analog_sig_source_x_0_1_0.set_sampling_freq(self.samp_rate)
|
||||
self.blocks_throttle2_0_0.set_sample_rate(self.samp_rate)
|
||||
print(f"[samp_rate] -> {self.samp_rate}")
|
||||
|
||||
def get_freq_off(self):
|
||||
return self.freq_off
|
||||
|
||||
def set_freq_off(self, freq_off):
|
||||
self.freq_off = freq_off
|
||||
self.analog_sig_source_x_0_1_0.set_frequency(self.freq_off)
|
||||
print(f"[freq_off] -> {self.freq_off}")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# ZMQ PULL listener
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _zmq_listener(self):
|
||||
while True:
|
||||
try:
|
||||
# Accept either a raw 8-byte double or a UTF-8 string
|
||||
raw = self._pull_sock.recv()
|
||||
if len(raw) == 8:
|
||||
val = struct.unpack('d', raw)[0] # double from C/numpy sender
|
||||
else:
|
||||
val = float(raw.decode('utf-8').strip())
|
||||
self.set_freq_off(self.freq_off + val)
|
||||
except (ValueError, struct.error) as e:
|
||||
print(f"[zmq] invalid message: {e}")
|
||||
except zmq.ZMQError as e:
|
||||
print(f"[zmq] error: {e}")
|
||||
break
|
||||
|
||||
def _zmq_listener_oai(self):
|
||||
while True:
|
||||
try:
|
||||
# Accept either a raw 8-byte double or a UTF-8 string
|
||||
raw = self._pull_sock_oai.recv()
|
||||
if len(raw) == 8:
|
||||
val = struct.unpack('d', raw)[0] # double from C/numpy sender
|
||||
else:
|
||||
val = float(raw.decode('utf-8').strip())
|
||||
self.set_rx_freq(val)
|
||||
self.set_freq_off(0)
|
||||
except (ValueError, struct.error) as e:
|
||||
print(f"[zmq] invalid message: {e}")
|
||||
except zmq.ZMQError as e:
|
||||
print(f"[zmq] error: {e}")
|
||||
break
|
||||
|
||||
def start(self):
|
||||
self._zmq_thread.start()
|
||||
self._zmq_thread_oai.start()
|
||||
super().start()
|
||||
|
||||
def main(top_block_cls=untitled, options=None):
|
||||
parser = ArgumentParser(description="Simple doppler injector")
|
||||
parser.add_argument(
|
||||
"--samp-rate", type=int, default=23040000,
|
||||
help="Sample rate in Hz (default: 23040000)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rx-freq", type=int, default=3619200000,
|
||||
help="Carrier freq in Hz (default: 3619200000)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--freq-off", type=float, default=0.0,
|
||||
help="Initial frequency offset in Hz (default: 0.0)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--zmq-req-src", type=str, default="tcp://127.0.0.1:4445",
|
||||
help="ZMQ REQ source address (default: tcp://127.0.0.1:4445)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--zmq-req-snk", type=str, default="tcp://127.0.0.1:4446",
|
||||
help="ZMQ REQ sink address (default: tcp://127.0.0.1:4446)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--zmq-pull-addr", type=str, default="tcp://*:5555",
|
||||
help="ZMQ PULL bind address for incoming freq_off updates (default: tcp://*:5555)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--zmq-pull-oai-addr", type=str, default="tcp://*:5556",
|
||||
help="ZMQ PULL bind address for incoming carrier_freq updates from OAI UE (default: tcp://*:5556)"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
tb = top_block_cls(
|
||||
samp_rate=args.samp_rate,
|
||||
rx_freq=args.rx_freq,
|
||||
freq_off=args.freq_off,
|
||||
zmq_req_src=args.zmq_req_src,
|
||||
zmq_req_snk=args.zmq_req_snk,
|
||||
zmq_pull_addr=args.zmq_pull_addr,
|
||||
zmq_pull_oai_addr=args.zmq_pull_oai_addr,
|
||||
)
|
||||
|
||||
def sig_handler(sig=None, frame=None):
|
||||
tb.stop()
|
||||
tb.wait()
|
||||
sys.exit(0)
|
||||
|
||||
signal.signal(signal.SIGINT, sig_handler)
|
||||
signal.signal(signal.SIGTERM, sig_handler)
|
||||
|
||||
tb.start()
|
||||
print(f"Flowgraph running: samp_rate={args.samp_rate}, rx_freq={args.rx_freq}")
|
||||
print(f"PULL (freq_off in): {args.zmq_pull_addr}")
|
||||
print(f"PULL (carrier_freq in): {args.zmq_pull_oai_addr}")
|
||||
|
||||
try:
|
||||
input('Press Enter to quit: ')
|
||||
except EOFError:
|
||||
pass
|
||||
|
||||
tb.stop()
|
||||
tb.wait()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Send a freq offset to the GNURadio doppler injector every second via ZMQ PUSH.
|
||||
|
||||
Usage:
|
||||
python3 send_freq_offset_zmq.py --carrier-freq 3489420000 --freq-off 50.0 --interval 0.5 --addr tcp://127.0.0.1:5555
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
import struct
|
||||
import signal
|
||||
import threading
|
||||
import argparse
|
||||
|
||||
import zmq
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Send freq offset to GNURadio via ZMQ PUSH")
|
||||
parser.add_argument(
|
||||
"--freq-off", type=float, default=50.0,
|
||||
help="Frequency offset in Hz to send (e.g. 50.0)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--addr", type=str, default="tcp://127.0.0.1:5555",
|
||||
help="GNURadio PULL address to send freq_off to (default: tcp://127.0.0.1:5555)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--interval", type=float, default=1.0,
|
||||
help="Send interval in seconds (default: 1.0)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--num-msg", type=int, default=15,
|
||||
help="Number of freq offsets to send (default: 15)"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
ctx = zmq.Context()
|
||||
# PUSH - send freq_off to GNURadio PULL
|
||||
push_sock = ctx.socket(zmq.PUSH)
|
||||
push_sock.setsockopt(zmq.LINGER, 0)
|
||||
push_sock.connect(args.addr)
|
||||
print(f"[zmq] PUSH connected to {args.addr}")
|
||||
|
||||
def sig_handler(sig, frame):
|
||||
print("\nStopping.")
|
||||
push_sock.close()
|
||||
ctx.term()
|
||||
sys.exit(0)
|
||||
|
||||
signal.signal(signal.SIGINT, sig_handler)
|
||||
signal.signal(signal.SIGTERM, sig_handler)
|
||||
|
||||
print(f"Sending freq_off = {args.freq_off} Hz every {args.interval}s - Ctrl+C to stop")
|
||||
|
||||
msg_count = 0
|
||||
while msg_count < args.num_msg:
|
||||
push_sock.send(struct.pack('d', args.freq_off))
|
||||
print(f"[sent] freq_offset = {args.freq_off} Hz")
|
||||
time.sleep(args.interval)
|
||||
msg_count += 1
|
||||
|
||||
print("\nStopping.")
|
||||
push_sock.close()
|
||||
ctx.term()
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -44,11 +44,13 @@
|
||||
#define ZMQ_SECTION "zmq"
|
||||
#define ZMQ_TX_CHANNELS "tx_channels"
|
||||
#define ZMQ_RX_CHANNELS "rx_channels"
|
||||
#define ZMQ_CTRL_CHANNELS "ctrl_channels"
|
||||
|
||||
#define ZMQ_PARAMS_DESC \
|
||||
{ \
|
||||
STRINGLISTPARAM(ZMQ_TX_CHANNELS, "list of zmq addresses represeting tx channels_\n", PARAMFLAG_MANDATORY, nullptr, nullptr), \
|
||||
STRINGLISTPARAM(ZMQ_RX_CHANNELS, "list of zmq addresses represeting rx channels_\n", PARAMFLAG_MANDATORY, nullptr, nullptr), \
|
||||
STRINGLISTPARAM(ZMQ_CTRL_CHANNELS, "list of zmq addresses represeting control channels_\n", PARAMFLAG_MANDATORY, nullptr, nullptr), \
|
||||
};
|
||||
|
||||
const size_t sample_size = sizeof(cf_t);
|
||||
@@ -56,6 +58,8 @@ const size_t rx_buffer_size = sample_size * 300000;
|
||||
|
||||
typedef struct {
|
||||
void *context;
|
||||
void *ctrl_context;
|
||||
void *ctrl_socket;
|
||||
zmq_tx_stream tx_stream;
|
||||
zmq_rx_stream rx_stream;
|
||||
std::thread poll_thread;
|
||||
@@ -224,6 +228,10 @@ static void zmq_end(openair0_device_t *device)
|
||||
|
||||
if (s->context)
|
||||
zmq_ctx_destroy(s->context);
|
||||
if (s->ctrl_socket)
|
||||
zmq_close(s->ctrl_socket);
|
||||
if (s->ctrl_context)
|
||||
zmq_ctx_destroy(s->ctrl_context);
|
||||
delete s;
|
||||
}
|
||||
}
|
||||
@@ -255,8 +263,18 @@ static int zmq_stop(openair0_device_t *device)
|
||||
|
||||
static int zmq_set_freq(openair0_device_t *device, openair0_config_t *openair0_cfg)
|
||||
{
|
||||
return 0;
|
||||
/* Send raw 8-byte little-endian double - matches struct.unpack('d') in Python */
|
||||
double freq = openair0_cfg[0].rx_freq[0];
|
||||
zmq_state_t *s = static_cast<zmq_state_t *>(device->priv);
|
||||
int rc = zmq_send(s->ctrl_socket, &freq, sizeof(double), 0);
|
||||
if (rc != sizeof(double)) {
|
||||
LOG_E(HW, "[ZMQ] send failed: %s\n", zmq_strerror(zmq_errno()));
|
||||
} else {
|
||||
LOG_I(HW, "[ZMQ] sent rx freq = %.4f Hz\n", freq);
|
||||
}
|
||||
return (rc == sizeof(double)) ? 0 : -1;
|
||||
}
|
||||
|
||||
static int zmq_set_gains(openair0_device_t *device, openair0_config_t *openair0_cfg)
|
||||
{
|
||||
return 0;
|
||||
@@ -325,6 +343,21 @@ extern "C" __attribute__((__visibility__("default"))) int device_init(openair0_d
|
||||
zmq_state->rx_stream.tx_stream_ = &zmq_state->tx_stream;
|
||||
}
|
||||
|
||||
// Setup control channel
|
||||
int num_ctrl_channels = gpd(param_desc, sizeofArray(param_desc), ZMQ_CTRL_CHANNELS)->numelt;
|
||||
if (num_ctrl_channels > 0) {
|
||||
AssertFatal(num_ctrl_channels == 1, "Only one ZMQ control channel is supported at the moment.\n");
|
||||
char **ctrl_channels = gpd(param_desc, sizeofArray(param_desc), ZMQ_CTRL_CHANNELS)->strlistptr;
|
||||
zmq_state->ctrl_context = zmq_ctx_new();
|
||||
zmq_state->ctrl_socket = zmq_socket(zmq_state->ctrl_context, ZMQ_PUSH);
|
||||
AssertFatal(zmq_state->ctrl_socket != NULL, "zmq_socket(ZMQ_PUSH) for control channel failed");
|
||||
int linger = 0;
|
||||
zmq_setsockopt(zmq_state->ctrl_socket, ZMQ_LINGER, &linger, sizeof(linger));
|
||||
AssertFatal(zmq_connect(zmq_state->ctrl_socket, ctrl_channels[0]) == 0,
|
||||
"zmq_bind for control channel failed on %s",
|
||||
ctrl_channels[0]);
|
||||
}
|
||||
|
||||
device->trx_start_func = zmq_start;
|
||||
device->trx_get_stats_func = zmq_get_stats;
|
||||
device->trx_reset_stats_func = zmq_reset_stats;
|
||||
|
||||
Reference in New Issue
Block a user