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
This commit is contained in:
Vadim Yanitskiy 2020-05-14 17:03:25 +07:00
parent 220e60184f
commit 7ec1c1ccc8
5 changed files with 140 additions and 18 deletions

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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: