arfcn
Version:
Utilities for working with Absolute Radio Frequency Channel Numbers (ARFCN) in cellular networks
251 lines (218 loc) • 6.88 kB
text/typescript
import { NrArfcnParameters } from './NrArfcnParameters'
import { NrBands } from './NrBands'
import { EutraBands } from './EutraBands'
import { NrArfcnBands } from './NrArfcnBands'
export enum LinkDirection {
Downlink,
Uplink,
Unspecified,
}
export function nrArfcnToFrequency(nrArfcn: number): number {
// NR-ARFCN range is defined to be [0 ... 3279165] in
// 3GPP TS 38.104 V18.8.0 (2024-12), Section 5.4.2.1
if (!Number.isFinite(nrArfcn) || nrArfcn < 0 || nrArfcn > 3279165) {
return -1
}
// As defined in 3GPP TS 38.104 V18.7.0 (2024-09), Section 5.4.2.1
for (let i = 0; i < NrArfcnParameters.rows.length++; i++) {
const _nparam = NrArfcnParameters.rows[i]
if (nrArfcn >= _nparam.n_ref_min && nrArfcn <= _nparam.n_ref_max) {
const f_offset = Math.floor(_nparam.f_ref_offs * 1000)
const delta_f = _nparam.df_global_khz
const n_offset = _nparam.n_ref_offs
const freq_khz = f_offset + delta_f * (nrArfcn - n_offset)
const freq_mhz = Math.round(freq_khz) / 1000
return freq_mhz
}
}
return -1
}
export function frequencyToNrBands(
frequencyMhz: number,
direction: LinkDirection = LinkDirection.Unspecified
): number[] {
// NR global frequency raster is defined for all frequencies between 0 and 100 GHz
// as per 3GPP TS 38.104 V18.8.0 (2024-12), Section 5.4.2.1
if (
!Number.isFinite(frequencyMhz) ||
frequencyMhz < 0 ||
frequencyMhz > 100000
) {
return []
}
const k = NrBands.rows
.filter((_band) => {
let matchDl,
matchUl = false
if (
(direction == LinkDirection.Downlink ||
direction == LinkDirection.Unspecified) &&
_band.f_dl_lo != null &&
_band.f_dl_hi != null
) {
matchDl = frequencyMhz >= _band.f_dl_lo && frequencyMhz <= _band.f_dl_hi
}
if (
(direction == LinkDirection.Uplink ||
direction == LinkDirection.Unspecified) &&
_band.f_ul_lo != null &&
_band.f_ul_hi != null
) {
matchUl = frequencyMhz >= _band.f_ul_lo && frequencyMhz <= _band.f_ul_hi
}
if (matchDl || matchUl) {
return true
}
return false
})
.map((_band) => _band.band)
return k
}
export function nrArfcnToBands(
nrArfcn: number,
direction: LinkDirection = LinkDirection.Unspecified
): number[] {
if (!Number.isFinite(nrArfcn) || nrArfcn < 0 || nrArfcn > 3279165) {
return []
}
const bands = NrArfcnBands.rows
.filter((_r) => {
let dl = false
if (
direction === LinkDirection.Downlink ||
direction === LinkDirection.Unspecified
) {
dl =
_r.n_dl_lo != null &&
_r.n_dl_hi != null &&
_r.n_dl_lo <= nrArfcn &&
_r.n_dl_hi >= nrArfcn
}
if (direction === LinkDirection.Downlink) {
return dl
}
let ul = false
if (
direction === LinkDirection.Uplink ||
direction === LinkDirection.Unspecified
) {
ul =
_r.n_ul_lo != null &&
_r.n_ul_hi != null &&
_r.n_ul_lo <= nrArfcn &&
_r.n_ul_hi >= nrArfcn
}
if (direction === LinkDirection.Uplink) {
return ul
}
return dl || ul
})
.map((_r) => _r.band)
return Array.from(new Set(bands))
}
export function frequencyToNrArfcn(
frequencyMhz: number,
roundNumber: boolean = false
): number {
if (
!Number.isFinite(frequencyMhz) ||
frequencyMhz < 0 ||
frequencyMhz > 100000
) {
return -1
}
for (const _param of NrArfcnParameters.rows) {
if (frequencyMhz >= _param.f_min && frequencyMhz <= _param.f_max) {
// 3GPP TS 38.104 V18.8.0 (2024-12) Section 5.4.2.1
// f_ref = f_ref_offs + df_global * (n_ref - n_ref_offs)
//
// solve for n_ref:
// n_ref = (f_ref - f_ref_offs) / df_global + n_ref_offs
const nrArfcn =
(frequencyMhz - _param.f_ref_offs) / (_param.df_global_khz / 1000) +
_param.n_ref_offs
return roundNumber ? Math.round(nrArfcn) : nrArfcn
}
}
return -1
}
export function earfcnToFrequency(earfcn: number): number {
// 3GPP TS 36.101 V18.7.0 (2024-09)
// 5.7.3 Carrier frequency and EARFCN
// valid range: 0 – 262143
// f_dl = f_dl_lo + 0.1 * (n_dl - n_offs_dl)
// f_ul = f_ul_lo + 0.1 * (n_ul - n_offs_ul)
// DL and UL EARFCN ranges do not overlap, so we don't need to ask for direction
// in the parameters. However, we have to keep track of which direction
// matched to know which formula to use to calculate the frequency.
// EARFCN range defined to be [0 ... 262143] in
// 3GPP TS 36.101 V19.0.1 (2024-12),
// Section 5.7.3 - Carrier frequency and EARFCN
if (!Number.isFinite(earfcn) || earfcn < 0 || earfcn > 262143) {
return -1
}
const match = EutraBands.rows.filter((_r) => {
// using != null because n_dl_lo can be converted to false if it's 0
// and 0 is a valid EARFCN value
return (
_r.n_dl_lo != null &&
_r.n_dl_lo <= earfcn &&
_r.n_dl_hi != null &&
_r.n_dl_hi >= earfcn
)
})
if (match.length > 0 && match[0]) {
return match[0].f_dl_lo! + 0.1 * (earfcn - match[0].n_offs_dl!)
}
return -1
}
export function frequencyToEutraBands(
frequencyMhz: number,
direction: LinkDirection = LinkDirection.Unspecified
): number[] {
if (!Number.isFinite(frequencyMhz) || frequencyMhz < 0) {
return []
}
const k = EutraBands.rows
.filter((_band) => {
let matchDl,
matchUl = false
if (
(direction === LinkDirection.Downlink ||
direction === LinkDirection.Unspecified) &&
_band.f_dl_lo &&
_band.f_dl_hi
) {
matchDl = frequencyMhz >= _band.f_dl_lo && frequencyMhz <= _band.f_dl_hi
}
if (
(direction === LinkDirection.Uplink ||
direction === LinkDirection.Unspecified) &&
_band.f_ul_lo &&
_band.f_ul_hi
) {
matchUl = frequencyMhz >= _band.f_ul_lo && frequencyMhz <= _band.f_ul_hi
}
return matchDl || matchUl
})
.map((_band) => _band.band)
return k
}
export function earfcnToBand(earfcn: number): number {
if (!Number.isFinite(earfcn) || earfcn < 0) {
return -1
}
for (const _r of EutraBands.rows) {
if (_r.n_dl_lo != null && _r.n_dl_hi != null) {
if (earfcn >= _r.n_dl_lo && earfcn <= _r.n_dl_hi) {
return _r.band
}
}
if (_r.n_ul_lo != null && _r.n_ul_hi != null) {
if (earfcn >= _r.n_ul_lo && earfcn <= _r.n_ul_hi) {
return _r.band
}
}
}
return -1
}