Commit 6cb9d37f authored by Kirill Smelkov's avatar Kirill Smelkov

earfcn: New package to do computations with LTE bands, frequencies and EARFCN numbers

Do a package which provides calculations like EARFCN -> frequency,
EARFCN -> band info, and to convert DL/UL EARFCN in between each other.

I was hoping to find something ready on the net, but could find only
pypi.org/project/nrarfcn for 5G, while for LTE everything I found
was of lesser quality and capability.

-> So do it myself.

See package documentation for API details.
parent bcfd82dd
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
XLTE repository provides assorted tools and packages with functionality related to LTE: XLTE repository provides assorted tools and packages with functionality related to LTE:
- `earfcn` - do computations with LTE bands, frequencies and EARFCN numbers.
- `kpi` - process measurements and compute KPIs from them. - `kpi` - process measurements and compute KPIs from them.
- `amari.drb` - infrastructure to process flows on data radio bearers. - `amari.drb` - infrastructure to process flows on data radio bearers.
- `amari.kpi` - driver for Amarisoft LTE stack to retrieve KPI-related measurements from logs. - `amari.kpi` - driver for Amarisoft LTE stack to retrieve KPI-related measurements from logs.
......
# Copyright (C) 2023 Nexedi SA and Contributors.
# Kirill Smelkov <kirr@nexedi.com>
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.
"""Package earfcn helps to do computations with LTE bands, frequencies and EARFCN numbers.
- frequency converts EARFCN to frequency.
- dl2ul and ul2dl convert between DL EARFCN and UL EARFCN corresponding to each other.
- band returns information about band to which EARFCN belongs.
See also pypi.org/project/nrarfcn which provides similar functionality for 5G.
"""
import collections
# TS 36.101 v16.18.0 Table 5.7.3-1
# band fdl_lo Noffs_dl NDL range ful_lo Noffs_ul NUL range
_tab573_1 = """\
1 2110 0 0 - 599 1920 18000 18000 - 18599
2 1930 600 600 - 1199 1850 18600 18600 - 19199
3 1805 1200 1200 - 1949 1710 19200 19200 - 19949
4 2110 1950 1950 - 2399 1710 19950 19950 - 20399
5 869 2400 2400 - 2649 824 20400 20400 - 20649
6 875 2650 2650 - 2749 830 20650 20650 - 20749
7 2620 2750 2750 - 3449 2500 20750 20750 - 21449
8 925 3450 3450 - 3799 880 21450 21450 - 21799
9 1844.9 3800 3800 - 4149 1749.9 21800 21800 - 22149
10 2110 4150 4150 - 4749 1710 22150 22150 - 22749
11 1475.9 4750 4750 - 4949 1427.9 22750 22750 - 22949
12 729 5010 5010 - 5179 699 23010 23010 - 23179
13 746 5180 5180 - 5279 777 23180 23180 - 23279
14 758 5280 5280 - 5379 788 23280 23280 - 23379
17 734 5730 5730 - 5849 704 23730 23730 - 23849
18 860 5850 5850 - 5999 815 23850 23850 - 23999
19 875 6000 6000 - 6149 830 24000 24000 - 24149
20 791 6150 6150 - 6449 832 24150 24150 - 24449
21 1495.9 6450 6450 - 6599 1447.9 24450 24450 - 24599
22 3510 6600 6600 - 7399 3410 24600 24600 - 25399
23 2180 7500 7500 - 7699 2000 25500 25500 - 25699
24 1525 7700 7700 - 8039 1626.5 25700 25700 - 26039
25 1930 8040 8040 - 8689 1850 26040 26040 - 26689
26 859 8690 8690 - 9039 814 26690 26690 - 27039
27 852 9040 9040 - 9209 807 27040 27040 - 27209
28 758 9210 9210 - 9659 703 27210 27210 - 27659
29 717 9660 9660 - 9769 N/A
30 2350 9770 9770 - 9869 2305 27660 27660 - 27759
31 462.5 9870 9870 - 9919 452.5 27760 27760 - 27809
32 1452 9920 9920 - 10359 N/A
33 1900 36000 36000 - 36199 1900 36000 36000 - 36199
34 2010 36200 36200 - 36349 2010 36200 36200 - 36349
35 1850 36350 36350 - 36949 1850 36350 36350 - 36949
36 1930 36950 36950 - 37549 1930 36950 36950 - 37549
37 1910 37550 37550 - 37749 1910 37550 37550 - 37749
38 2570 37750 37750 - 38249 2570 37750 37750 - 38249
39 1880 38250 38250 - 38649 1880 38250 38250 - 38649
40 2300 38650 38650 - 39649 2300 38650 38650 - 39649
41 2496 39650 39650 - 41589 2496 39650 39650 - 41589
42 3400 41590 41590 - 43589 3400 41590 41590 - 43589
43 3600 43590 43590 - 45589 3600 43590 43590 - 45589
44 703 45590 45590 - 46589 703 45590 45590 - 46589
45 1447 46590 46590 - 46789 1447 46590 46590 - 46789
46 5150 46790 46790 - 54539 5150 46790 46790 - 54539
47 5855 54540 54540 - 55239 5855 54540 54540 - 55239
48 3550 55240 55240 - 56739 3550 55240 55240 - 56739
49 3550 56740 56740 - 58239 3550 56740 56740 - 58239
50 1432 58240 58240 - 59089 1432 58240 58240 - 59089
51 1427 59090 59090 - 59139 1427 59090 59090 - 59139
52 3300 59140 59140 - 60139 3300 59140 59140 - 60139
53 2483.5 60140 60140 - 60254 2483.5 60140 60140 - 60254
64 Reserved
65 2110 65536 65536 - 66435 1920 131072 131072 - 131971
66 2110 66436 66436 - 67335 1710 131972 131972 - 132671
67 738 67336 67336 - 67535 N/A
68 753 67536 67536 - 67835 698 132672 132672 - 132971
69 2570 67836 67836 - 68335 N/A
70 1995 68336 68336 - 68585 1695 132972 132972 - 133121
71 617 68586 68586 - 68935 663 133122 133122 - 133471
72 461 68936 68936 - 68985 451 133472 133472 - 133521
73 460 68986 68986 - 69035 450 133522 133522 - 133571
74 1475 69036 69036 - 69465 1427 133572 133572 - 134001
75 1432 69466 69466 - 70315 N/A
76 1427 70316 70316 - 70365 N/A
85 728 70366 70366 - 70545 698 134002 134002 - 134181
87 420 70546 70546 - 70595 410 134182 134182 - 134231
88 422 70596 70596 - 70645 412 134232 134232 - 134281
"""
# Band represents information about one LTE band.
Band = collections.namedtuple('Band',
['band',
'fdl_lo', 'fdl_hi_', 'ndl_lo', 'ndl_hi', # [fdl_lo, fdl_hi_) [ndl_lo, ndl_hi]
'ful_lo', 'ful_hi_', 'nul_lo', 'nul_hi', # [ful_lo, ful_hi_) [nul_lo, nul_hi]
'rf_mode'])
# _band_tab is table with all LTE bands.
_band_tab = [] # of Band
def _():
prev = None
for l in _tab573_1.splitlines():
v = l.split()
assert len(v) > 0, v
if len(v) == 1:
assert v[0] == '…', v
continue
band = int(v[0])
if len(v) == 2:
assert v[1] == 'Reserved', v
continue
assert len(v) >= 6, v
fdl_lo = float(v[1])
noff_dl = int (v[2])
ndl_lo = int (v[3])
assert v[4] == '-', v
ndl_hi = int (v[5])
assert noff_dl == ndl_lo, v
assert ndl_lo < ndl_hi, v
fdl_hi_ = fdl_lo + 0.1*(ndl_hi-ndl_lo+1) # ~ TS 36.101 5.7.3
if len(v) == 7:
assert v[6] == 'N/A'
ful_lo = None
ful_hi_ = None
nul_lo = None
nul_hi = None
else:
assert len(v) == 11, v
ful_lo = float(v[6])
noff_ul = int (v[7])
nul_lo = int (v[8])
assert v[9] == '-', v
nul_hi = int (v[10])
assert noff_ul == nul_lo, v
assert nul_lo < nul_hi, v
ful_hi_ = ful_lo + 0.1*(nul_hi-nul_lo+1) # ~ TS 36.101 5.7.3
assert (ndl_hi - ndl_lo) >= (nul_hi - nul_lo), ((ndl_lo, ndl_hi), (nul_lo, nul_hi))
if ful_lo is None:
rf_mode = 'SDL'
elif ful_lo == fdl_lo:
assert (nul_lo, nul_hi) == (ndl_lo, ndl_hi)
rf_mode = 'TDD'
else:
assert (nul_lo, nul_hi) != (ndl_lo, ndl_hi)
rf_mode = 'FDD'
band = Band(band,
fdl_lo, fdl_hi_, ndl_lo, ndl_hi,
ful_lo, ful_hi_, nul_lo, nul_hi,
rf_mode)
if prev is not None:
n = band
p = prev
assert p.band < n.band
assert p.ndl_hi < n.ndl_lo
if p.ful_lo is not None and n.ful_lo is not None:
assert p.nul_hi < n.nul_hi
_band_tab.append(band)
prev = band
_()
# band returns information about band covering earfcn.
def band(earfcn): # -> (Band, is_dl) | KeyError
try:
b = _band_lookup_dl(earfcn)
except KeyError:
pass
else:
return b, True
try:
b = _band_lookup_ul(earfcn)
except KeyError:
pass
else:
return b, False
raise KeyError('no band that corresponds to EARFCN=%r' % earfcn)
# _band_lookup_{dl,ul} look up Band by DL/UL EARFCN.
def _band_lookup_dl(dl_earfcn): # -> Band | KeyError
# TODO linear search -> bsearch
for band in _band_tab:
if band.ndl_lo <= dl_earfcn <= band.ndl_hi:
return band
raise KeyError('no band that corresponds to DL EARFCN=%r' % dl_earfcn)
def _band_lookup_ul(ul_earfcn): # -> Band | KeyError
# TODO linear search -> bsearch
for band in _band_tab:
if band.ful_lo is None:
continue
if band.nul_lo <= ul_earfcn <= band.nul_hi:
return band
raise KeyError('no band that corresponds to UL EARFCN=%r' % ul_earfcn)
# dl2ul returns UL EARFCN that corresponds to DL EARFCN.
def dl2ul(dl_earfcn): # -> ul_earfcn
b = _band_lookup_dl(dl_earfcn)
assert b.ndl_lo <= dl_earfcn <= b.ndl_hi
if b.ful_lo is None:
raise KeyError('band%r, to which DL EARFCN=%r belongs, does not have uplink spectrum' % (b.band, dl_earfcn))
if dl_earfcn - b.ndl_lo > b.nul_hi - b.nul_lo:
raise KeyError('band%r does not have enough uplink spectrum to provide pair for DL EARFCN=%r' % (b.band, dl_earfcn))
ul_earfcn = b.nul_lo + (dl_earfcn - b.ndl_lo)
assert b.nul_lo <= ul_earfcn <= b.nul_hi
return ul_earfcn
# ul2dl returns DL EARFCN that corresponds to UL EARFCN.
def ul2dl(ul_earfcn): # -> dl_earfcn
b = _band_lookup_ul(ul_earfcn)
assert b.nul_lo <= ul_earfcn <= b.nul_hi
assert b.fdl_lo is not None
dl_earfcn = b.ndl_lo + (ul_earfcn - b.nul_lo)
assert b.ndl_lo <= dl_earfcn <= b.ndl_hi
return dl_earfcn
# frequency returns frequency corresponding to DL or UL EARFCN.
def frequency(earfcn): # -> freq (MHz)
b, dl = band(earfcn)
if dl:
assert b.ndl_lo <= earfcn <= b.ndl_hi
fdl = b.fdl_lo + 0.1*(earfcn - b.ndl_lo) # TS 36.101 5.7.3
return fdl
else:
assert b.nul_lo <= earfcn <= b.nul_hi
ful = b.ful_lo + 0.1*(earfcn - b.nul_lo) # TS 36.101 5.7.3
return ful
# Copyright (C) 2023 Nexedi SA and Contributors.
# Kirill Smelkov <kirr@nexedi.com>
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.
from xlte.earfcn import frequency, dl2ul, ul2dl, _band_tab
from xlte import earfcn
from pytest import raises
# verify earfcn-related calculations wrt several points.
def test_earfcn():
def _(band, dl_earfcn, ul_earfcn, fdl, ful, rf_mode, fdl_lo, fdl_hi_, ful_lo, ful_hi_):
assert frequency(dl_earfcn) == fdl
if ul_earfcn is not None:
assert dl2ul(dl_earfcn) == ul_earfcn
else:
assert rf_mode in ('FDD', 'SDL')
if rf_mode == 'FDD':
estr = 'does not have enough uplink spectrum'
if rf_mode == 'SDL':
estr = 'does not have uplink spectrum'
with raises(KeyError, match=estr):
dl2ul(dl_earfcn)
b, isdl = earfcn.band(dl_earfcn)
assert isdl == True
if ul_earfcn is not None:
assert frequency(ul_earfcn) == ful
assert ul2dl(ul_earfcn) == dl_earfcn
b_, isdl_ = earfcn.band(ul_earfcn)
assert isdl_ == (rf_mode == 'TDD')
assert b == b_
assert b.band == band
assert b.rf_mode == rf_mode
assert b.fdl_lo == fdl_lo
assert b.fdl_hi_ == fdl_hi_
assert b.ful_lo == ful_lo
assert b.ful_hi_ == ful_hi_
# band dl ul fdl ful rf_mode fdl_lo fdl_hi_ ful_lo ful_hi_
_( 1, 300, 18300, 2140, 1950, 'FDD', 2110, 2170, 1920, 1980)
_(37, 37555, 37555, 1910.5, 1910.5, 'TDD', 1910, 1930, 1910, 1930)
_(29, 9700, None, 721, None, 'SDL', 717, 728, None, None)
_(66, 67135, 132671, 2179.9, 1779.9, 'FDD', 2110, 2200, 1710, 1780)
_(66, 67136, None, 2180, None, 'FDD', 2110, 2200, 1710, 1780) # NOTE B66 has different amount in dl and ul ranges
# verify that earfcn regions of all bands do not overlap.
def test_bands_no_earfcn_overlap():
rv = [] # of (nlo, nhi)
for b in _band_tab:
assert b.ndl_lo is not None
assert b.ndl_hi is not None
rv.append((b.ndl_lo, b.ndl_hi))
if b.rf_mode not in ('TDD', 'SDL'):
assert b.nul_lo is not None
assert b.nul_hi is not None
rv.append((b.nul_lo, b.nul_hi))
for i in range(len(rv)):
ilo, ihi = rv[i]
assert ilo < ihi
for j in range(len(rv)):
if j == i:
continue
jlo, jhi = rv[j]
assert jlo < jhi
if not ((ihi < jlo) or (jhi < ilo)):
assert False, "(%r, %r) overlaps with (%r, %r)" % (ilo, ihi, jlo, jhi)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment