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:
parent
220e60184f
commit
7ec1c1ccc8
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue