summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--dpd/dpd.ini5
-rwxr-xr-xdpd/iq_file_server.py121
2 files changed, 126 insertions, 0 deletions
diff --git a/dpd/dpd.ini b/dpd/dpd.ini
index 9ba2645..ea42b72 100644
--- a/dpd/dpd.ini
+++ b/dpd/dpd.ini
@@ -25,8 +25,13 @@ enabled=1
polycoeffile=polyCoefs
[output]
+# to prepare a file for the dpd/iq_file_server.py script,
+# use output=file
output=uhd
+[fileoutput]
+filename=dpd.iq
+
[uhdoutput]
device=
master_clock_rate=32768000
diff --git a/dpd/iq_file_server.py b/dpd/iq_file_server.py
new file mode 100755
index 0000000..2a38151
--- /dev/null
+++ b/dpd/iq_file_server.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# This example server simulates the ODR-DabMod's
+# DPD server, taking samples from an IQ file
+#
+# Copyright (C) 2017 Matthias P. Braendli
+# http://www.opendigitalradio.org
+# Licence: The MIT License, see notice at the end of this file
+
+import sys
+import socket
+import struct
+import argparse
+import numpy as np
+from datetime import datetime
+
+SIZEOF_SAMPLE = 8 # complex floats
+# Constants for TM 1
+NbSymbols = 76
+NbCarriers = 1536
+Spacing = 2048
+NullSize = 2656
+SymSize = 2552
+FicSizeOut = 288
+FrameSize = NullSize + NbSymbols*SymSize
+
+def main():
+ parser = argparse.ArgumentParser(description="Simulate ODR-DabMod DPD server")
+ parser.add_argument('--port', default='50055',
+ help='port to listen on (default: 50055)',
+ required=False)
+ parser.add_argument('--file', help='I/Q File to read from (complex floats)',
+ required=True)
+ parser.add_argument('--samplerate', default='8192000', help='Sample rate',
+ required=False)
+
+ cli_args = parser.parse_args()
+
+ serve_file(cli_args)
+
+def recv_exact(sock, num_bytes):
+ bufs = []
+ while num_bytes > 0:
+ b = sock.recv(num_bytes)
+ if len(b) == 0:
+ break
+ num_bytes -= len(b)
+ bufs.append(b)
+ return b''.join(bufs)
+
+def serve_file(options):
+ oversampling = int(int(options.samplerate) / 2048000)
+ consumesamples = 8*FrameSize * oversampling
+ iq_data = np.fromfile(options.file, count=consumesamples, dtype=np.complex64)
+
+ print("Loaded {} samples of IQ data".format(len(iq_data)))
+
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.bind(('localhost', int(options.port)))
+ s.listen()
+
+ try:
+ while True:
+ sock, addr_info = s.accept()
+ print("Got a connection from {}".format(addr_info))
+
+ ver = recv_exact(sock, 1)
+ (num_samps,) = struct.unpack("=I", recv_exact(sock, 4))
+ num_bytes = num_samps * SIZEOF_SAMPLE
+
+ if num_bytes > len(iq_data):
+ print("Truncating length to {} samples".format(len(iq_data)))
+ num_samps = len(iq_data)
+ num_bytes = num_samps * 4
+
+ tx_sec = datetime.now().timestamp()
+ tx_pps = int(16384000 * (tx_sec - int(tx_sec)))
+ tx_second = int(tx_sec)
+
+ # TX metadata and data
+ sock.sendall(struct.pack("=III", num_samps, tx_second, tx_pps))
+ sock.sendall(iq_data[-num_samps:].tobytes())
+
+ # RX metadata and data
+ rx_second = tx_second + 1
+ rx_pps = tx_pps
+ sock.sendall(struct.pack("=III", num_samps, rx_second, rx_pps))
+ sock.sendall(iq_data[-num_samps:].tobytes())
+
+ print("Sent {} samples".format(num_samps))
+
+ sock.close()
+ finally:
+ s.close()
+ raise
+
+main()
+
+
+# The MIT License (MIT)
+#
+# Copyright (c) 2017 Matthias P. Braendli
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.