summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVadim Yanitskiy <vyanitskiy@sysmocom.de>2020-05-14 17:03:25 +0700
committerVadim Yanitskiy <vyanitskiy@sysmocom.de>2020-05-17 14:36:12 +0700
commit7ec1c1ccc84536db35fa3c51eea6ef291a762cda (patch)
tree1e6c880a4a8a54bd8de46b49aadd9ec91a423d55
parent220e60184ffccbebf650d07d2aefd5ab4d375cce (diff)
trx_toolkit/transceiver.py: add frequency hopping support
There are two ways to implement frequency hopping: a) The Transceiver is configured with the hopping parameters, in particular HSN, MAIO, and the list of ARFCNs (channels), so the actual Rx/Tx frequencies are changed by the Transceiver itself depending on the current TDMA frame number. b) The L1 maintains several Transceivers (two or more), so each instance is assigned one dedicated RF carrier frequency, and hence the number of available hopping frequencies is equal to the number of Transceivers. In this case, it's the task of the L1 to commutate bursts between Transceivers (frequencies). Variant a) is commonly known as "synthesizer frequency hopping" whereas b) is known as "baseband frequency hopping". For the MS side, a) is preferred, because a phone usually has only one Transceiver (per RAT). On the other hand, b) is more suitable for the BTS side, because it's relatively easy to implement and there is no technical limitation on the amount of Transceivers. FakeTRX obviously does support b) since multi-TRX feature has been implemented, as well as a) by resolving UL/DL frequencies using a preconfigured (by the L1) set of the hopping parameters. The later can be enabled using the SETFH control command: CMD SETFH <HSN> <MAIO> <RXF1> <TXF1> [... <RXFN> <TXFN>] where <RXFN> and <TXFN> is a pair of Rx/Tx frequencies (in kHz) corresponding to one ARFCN the Mobile Allocation. Note that the channel list is expected to be sorted in ascending order. NOTE: in the current implementation, mode a) applies to the whole Transceiver and all its timeslots, so using in for the BTS side does not make any sense (imagine BCCH hopping together with DCCH). Change-Id: I587e4f5da67c7b7f28e010ed46b24622c31a3fdd
-rw-r--r--src/target/trx_toolkit/burst_fwd.py13
-rw-r--r--src/target/trx_toolkit/ctrl_if.py11
-rw-r--r--src/target/trx_toolkit/ctrl_if_trx.py39
-rw-r--r--src/target/trx_toolkit/fake_pm.py9
-rw-r--r--src/target/trx_toolkit/transceiver.py86
5 files changed, 140 insertions, 18 deletions
diff --git a/src/target/trx_toolkit/burst_fwd.py b/src/target/trx_toolkit/burst_fwd.py
index fd6013bd..9fb308f9 100644
--- a/src/target/trx_toolkit/burst_fwd.py
+++ b/src/target/trx_toolkit/burst_fwd.py
@@ -4,7 +4,8 @@
# TRX Toolkit
# Burst forwarding between transceivers
#
-# (C) 2017-2018 by Vadim Yanitskiy <axilirator@gmail.com>
+# (C) 2017-2020 by Vadim Yanitskiy <axilirator@gmail.com>
+# Contributions by sysmocom - s.f.m.c. GmbH
#
# All Rights Reserved
#
@@ -65,6 +66,10 @@ class BurstForwarder:
self.trx_list.remove(trx)
def forward_msg(self, src_trx, rx_msg):
+ # Originating Transceiver may use frequency hopping,
+ # so let's precalculate its Tx frequency in advance
+ tx_freq = src_trx.get_tx_freq(rx_msg.fn)
+
# Iterate over all known transceivers
for trx in self.trx_list:
if trx == src_trx:
@@ -73,11 +78,13 @@ class BurstForwarder:
# Check transceiver state
if not trx.running:
continue
- if trx.rx_freq != src_trx.tx_freq:
- continue
if rx_msg.tn not in trx.ts_list:
continue
+ # Match Tx/Rx frequencies of the both transceivers
+ if trx.get_rx_freq(rx_msg.fn) != tx_freq:
+ continue
+
# Transform from L12TRX to TRX2L1 and forward
tx_msg = rx_msg.gen_trx2l1(ver = trx.data_if._hdr_ver)
trx.handle_data_msg(src_trx, rx_msg, tx_msg)
diff --git a/src/target/trx_toolkit/ctrl_if.py b/src/target/trx_toolkit/ctrl_if.py
index 89dfe467..73335017 100644
--- a/src/target/trx_toolkit/ctrl_if.py
+++ b/src/target/trx_toolkit/ctrl_if.py
@@ -4,7 +4,8 @@
# TRX Toolkit
# CTRL interface implementation
#
-# (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
+# (C) 2016-2020 by Vadim Yanitskiy <axilirator@gmail.com>
+# Contributions by sysmocom - s.f.m.c. GmbH
#
# All Rights Reserved
#
@@ -61,13 +62,17 @@ class CTRLInterface(UDPLink):
# Now we have something like ["TXTUNE", "941600"]
return request
- def verify_cmd(self, request, cmd, argc):
+ # If va is True, the command can have variable number of arguments
+ def verify_cmd(self, request, cmd, argc, va = False):
# Check if requested command matches
if request[0] != cmd:
return False
# And has enough arguments
- if len(request) - 1 != argc:
+ req_len = len(request[1:])
+ if not va and req_len != argc:
+ return False
+ elif va and req_len < argc:
return False
return True
diff --git a/src/target/trx_toolkit/ctrl_if_trx.py b/src/target/trx_toolkit/ctrl_if_trx.py
index e44f8a92..65198cfe 100644
--- a/src/target/trx_toolkit/ctrl_if_trx.py
+++ b/src/target/trx_toolkit/ctrl_if_trx.py
@@ -4,7 +4,8 @@
# TRX Toolkit
# CTRL interface implementation (common commands)
#
-# (C) 2016-2019 by Vadim Yanitskiy <axilirator@gmail.com>
+# (C) 2016-2020 by Vadim Yanitskiy <axilirator@gmail.com>
+# Contributions by sysmocom - s.f.m.c. GmbH
#
# All Rights Reserved
#
@@ -105,9 +106,9 @@ class CTRLInterfaceTRX(CTRLInterface):
log.error("(%s) Transceiver already started" % self.trx)
return -1
- # Ensure RX / TX freq. are set
- if (self.trx.rx_freq is None) or (self.trx.tx_freq is None):
- log.error("(%s) RX / TX freq. are not set" % self.trx)
+ # Ensure that transceiver is ready
+ if not self.trx.ready:
+ log.error("(%s) Transceiver is not ready" % self.trx)
return -1
log.info("(%s) Starting transceiver..." % self.trx)
@@ -134,14 +135,14 @@ class CTRLInterfaceTRX(CTRLInterface):
log.debug("(%s) Recv RXTUNE cmd" % self.trx)
# TODO: check freq range
- self.trx.rx_freq = int(request[1]) * 1000
+ self.trx._rx_freq = int(request[1]) * 1000
return 0
elif self.verify_cmd(request, "TXTUNE", 1):
log.debug("(%s) Recv TXTUNE cmd" % self.trx)
# TODO: check freq range
- self.trx.tx_freq = int(request[1]) * 1000
+ self.trx._tx_freq = int(request[1]) * 1000
return 0
elif self.verify_cmd(request, "SETSLOT", 2):
@@ -187,6 +188,32 @@ class CTRLInterfaceTRX(CTRLInterface):
return (0, [str(meas_dbm)])
+ # Frequency hopping configuration (variable length list):
+ #
+ # CMD SETFH <HSN> <MAIO> <RXF1> <TXF1> [... <RXFN> <TXFN>]
+ #
+ # where <RXFN> and <TXFN> is a pair of Rx/Tx frequencies (in kHz)
+ # corresponding to one ARFCN the Mobile Allocation. Note that the
+ # channel list is expected to be sorted in ascending order.
+ if self.verify_cmd(request, "SETFH", 4, va = True):
+ log.debug("(%s) Recv SETFH cmd" % self.trx)
+
+ # Parse HSN and MAIO
+ hsn = int(request[1])
+ maio = int(request[2])
+
+ # Parse the list of hopping frequencies
+ ma = [int(f) * 1000 for f in request[3:]] # kHz -> Hz
+ ma = [(rx, tx) for rx, tx in zip(ma[0::2], ma[1::2])]
+
+ # Configure the hopping sequence generator
+ try:
+ self.trx.enable_fh(hsn, maio, ma)
+ return 0
+ except:
+ log.error("(%s) Failed to configure frequency hopping" % trx)
+ return -1
+
# TRXD header version negotiation
if self.verify_cmd(request, "SETFORMAT", 1):
log.debug("(%s) Recv SETFORMAT cmd" % self.trx)
diff --git a/src/target/trx_toolkit/fake_pm.py b/src/target/trx_toolkit/fake_pm.py
index 51dc0576..0d66099e 100644
--- a/src/target/trx_toolkit/fake_pm.py
+++ b/src/target/trx_toolkit/fake_pm.py
@@ -64,14 +64,19 @@ class FakePM:
def rssi_trx(self):
return randint(self.trx_min, self.trx_max)
- def measure(self, freq):
+ def measure(self, freq, fn = None):
# Iterate over all known transceivers
for trx in self.trx_list:
if not trx.running:
continue
+ # FIXME: we need to know current TDMA frame number here,
+ # because some transceivers may use frequency hopping
+ if trx.fh is not None and fn is None:
+ continue
+
# Match by given frequency
- if trx.tx_freq == freq:
+ if trx.get_tx_freq(fn) == freq:
return self.rssi_trx
return self.rssi_noise
diff --git a/src/target/trx_toolkit/transceiver.py b/src/target/trx_toolkit/transceiver.py
index b1a5c117..655d4f64 100644
--- a/src/target/trx_toolkit/transceiver.py
+++ b/src/target/trx_toolkit/transceiver.py
@@ -4,7 +4,8 @@
# TRX Toolkit
# Transceiver implementation
#
-# (C) 2018-2019 by Vadim Yanitskiy <axilirator@gmail.com>
+# (C) 2018-2020 by Vadim Yanitskiy <axilirator@gmail.com>
+# Contributions by sysmocom - s.f.m.c. GmbH
#
# All Rights Reserved
#
@@ -29,6 +30,8 @@ from data_if import DATAInterface
from udp_link import UDPLink
from trx_list import TRXList
+from gsm_shared import HoppingParams
+
class Transceiver:
""" Base transceiver implementation.
@@ -88,6 +91,38 @@ class Transceiver:
that shall provide at least one method: measure(freq). This
is required for the MS side (i.e. OsmocomBB).
+ == Frequency hopping (optional)
+
+ There are two ways to implement frequency hopping:
+
+ a) The Transceiver is configured with the hopping parameters, in
+ particular HSN, MAIO, and the list of ARFCNs (channels), so the
+ actual Rx/Tx frequencies are changed by the Transceiver itself
+ depending on the current TDMA frame number.
+
+ b) The L1 maintains several Transceivers (two or more), so each
+ instance is assigned one dedicated RF carrier frequency, and
+ hence the number of available hopping frequencies is equal to
+ the number of Transceivers. In this case, it's the task of
+ the L1 to commutate bursts between Transceivers (frequencies).
+
+ Variant a) is commonly known as "synthesizer frequency hopping"
+ whereas b) is known as "baseband frequency hopping".
+
+ For the MS side, a) is preferred, because a phone usually has only
+ one Transceiver (per RAT). On the other hand, b) is more suitable
+ for the BTS side, because it's relatively easy to implement and
+ there is no technical limitation on the amount of Transceivers.
+
+ FakeTRX obviously does support b) since multi-TRX feature has been
+ implemented, as well as a) by resolving UL/DL frequencies using a
+ preconfigured (by the L1) set of the hopping parameters. The later
+ can be enabled using the SETFH control command.
+
+ NOTE: in the current implementation, mode a) applies to the whole
+ Transceiver and all its timeslots, so using in for the BTS side
+ does not make any sense (imagine BCCH hopping together with DCCH).
+
"""
def __init__(self, bind_addr, remote_addr, base_port, name = None,
@@ -131,8 +166,11 @@ class Transceiver:
self.running = False
# Actual RX / TX frequencies
- self.rx_freq = None
- self.tx_freq = None
+ self._rx_freq = None
+ self._tx_freq = None
+
+ # Frequency hopping parameters (set by CTRL)
+ self.fh = None
# List of active (configured) timeslots
self.ts_list = []
@@ -149,6 +187,41 @@ class Transceiver:
return desc
+ @property
+ def ready(self):
+ # Make sure that either both Rx/Tx frequencies are set
+ if self._rx_freq is None or self._tx_freq is None:
+ # ... or frequency hopping is in use
+ if self.fh is None:
+ return False
+
+ return True
+
+ def get_rx_freq(self, fn):
+ if self.fh is None:
+ return self._rx_freq
+
+ # Frequency hopping in use, resolve by TDMA fn
+ (rx_freq, _) = self.fh.resolve(fn)
+ return rx_freq
+
+ def get_tx_freq(self, fn):
+ if self.fh is None:
+ return self._tx_freq
+
+ # Frequency hopping in use, resolve by TDMA fn
+ (_, tx_freq) = self.fh.resolve(fn)
+ return tx_freq
+
+ def enable_fh(self, *args):
+ self.fh = HoppingParams(*args)
+ log.info("(%s) Frequency hopping configured: %s" % (self, self.fh))
+
+ def disable_fh(self):
+ if self.fh is not None:
+ log.info("(%s) Frequency hopping disabled" % self)
+ self.fh = None
+
# To be overwritten if required,
# no custom command handlers by default
def ctrl_cmd_handler(self, request):
@@ -159,8 +232,13 @@ class Transceiver:
for trx in self.child_trx_list.trx_list:
if event == "POWERON":
trx.running = True
- else:
+ elif event == "POWEROFF":
trx.running = False
+ trx.disable_fh()
+
+ # Reset frequency hopping parameters
+ if event == "POWEROFF":
+ self.disable_fh()
# Trigger clock generator if required
if self.clck_gen is not None: