"""射频合规参数与校验 — 标准第 8 章。
实现 TXS-10002-2025 标准中 8.2-8.4 定义的窄带/UWB 射频指标:
- 8.2.1 输出功率等级
- 8.2.2 调制精度 (GFSK 频偏 / PSK EVM / 频率容限 / 时钟精度)
- 8.2.3 无用发射 (GFSK/PSK 频段内杂散)
- 8.3.1 接收灵敏度与最大输入电平
- 8.3.2 接收机选择性
- 8.3.4 RSSI 精度
- 8.4 UWB 射频 (信道、频谱模板、频率容限、均方根误差)
"""
from __future__ import annotations
__all__ = [
"ACTIVE_CLOCK_JITTER_US",
"ACTIVE_CLOCK_PPM",
"MAX_INPUT_LEVEL_DBM",
"RSSI_ACCURACY_DB",
"RSSI_SIGNAL_OFFSET_DB",
"SLEEP_CLOCK_JITTER_US",
"SLEEP_CLOCK_PPM",
"SUB_1G_TOLERANCE_PPM",
"UWB_CLOCK_TOLERANCE_PPM",
"UWB_FREQ_TOLERANCE_PPM",
"UWB_MAX_NRMSE_DIFF_DB",
"UWB_MAX_NRMSE_PCT",
"ChannelBandwidth",
"EVMLimit",
"FreqBand",
"FreqToleranceSpec",
"GFSKFreqDevResult",
"GFSKFreqDevSpec",
"IntermodEntry",
"OutOfBandEntry",
"PSKModulation",
"PSKSpectrumMask",
"PowerClass",
"RFComplianceReport",
"SelectivityEntry",
"UWBSpectrumMask",
"check_clock_accuracy",
"check_evm",
"check_freq_tolerance",
"check_gfsk_freq_dev",
"check_rssi_accuracy",
"check_rx_spurious_emission",
"check_uwb_nrmse",
"classify_power",
"get_psk_spectrum_mask",
"gfsk_inband_spurious_limit_dbm",
"reference_sensitivity",
"selectivity_freq_offsets",
"uwb_channel_center_freq_mhz",
"validate_power_step",
]
from dataclasses import dataclass, field
from enum import IntEnum
import numpy as np
# ---------------------------------------------------------------------------
# 8.2.1 输出功率等级 (表 52)
# ---------------------------------------------------------------------------
[文档]
class PowerClass(IntEnum):
CLASS_0 = 0 # Pmax > 20 dBm
CLASS_1 = 1 # 17 < Pmax <= 20 dBm
CLASS_2 = 2 # 14 < Pmax <= 17 dBm
CLASS_3 = 3 # 10 < Pmax <= 14 dBm
CLASS_4 = 4 # 4 < Pmax <= 10 dBm
CLASS_5 = 5 # 0 < Pmax <= 4 dBm
CLASS_6 = 6 # Pmax <= 0 dBm
_POWER_CLASS_UPPER = {
PowerClass.CLASS_0: float('inf'),
PowerClass.CLASS_1: 20.0,
PowerClass.CLASS_2: 17.0,
PowerClass.CLASS_3: 14.0,
PowerClass.CLASS_4: 10.0,
PowerClass.CLASS_5: 4.0,
PowerClass.CLASS_6: 0.0,
}
_POWER_CLASS_LOWER = {
PowerClass.CLASS_0: 20.0,
PowerClass.CLASS_1: 17.0,
PowerClass.CLASS_2: 14.0,
PowerClass.CLASS_3: 10.0,
PowerClass.CLASS_4: 4.0,
PowerClass.CLASS_5: 0.0,
PowerClass.CLASS_6: float('-inf'),
}
[文档]
def classify_power(pmax_dbm: float) -> PowerClass:
"""根据最大输出功率确定功率等级。"""
for cls in PowerClass:
lo = _POWER_CLASS_LOWER[cls]
hi = _POWER_CLASS_UPPER[cls]
if lo < pmax_dbm <= hi:
return cls
if pmax_dbm > 20.0:
return PowerClass.CLASS_0
return PowerClass.CLASS_6
[文档]
def validate_power_step(prev_dbm: float, curr_dbm: float) -> bool:
"""校验相邻发射功率差是否不大于 8 dB (8.2.1)。"""
return abs(curr_dbm - prev_dbm) <= 8.0
# ---------------------------------------------------------------------------
# 8.2.2.1 GFSK 频率偏差 (表 + 文字)
# ---------------------------------------------------------------------------
[文档]
@dataclass
class GFSKFreqDevSpec:
"""GFSK 调制频偏规范 — 针对单个符号速率。"""
symbol_rate_msps: float
symbol_interval_us: float
min_deviation_khz: float # 任意序列最小频偏
fd1_min_khz: float # 11110000 序列 fd1 下限
fd1_max_khz: float # 11110000 序列 fd1 上限
fd2_min_khz: float # 10101010 序列 fd2 下限
fd2_fd1_ratio_min: float = 0.8 # fd2/fd1 均值比最小值
zero_crossing_margin: float = 0.125 # 过零误差 < ±1/8 符号周期
# 标准表中 7 种符号速率的规范
GFSK_FREQ_DEV_SPECS: dict[float, GFSKFreqDevSpec] = {
0.1: GFSKFreqDevSpec(
0.1, 10.0, 18.5, 22.5, 27.5, 18.5),
0.125: GFSKFreqDevSpec(
0.125, 8.0, 23.125, 28.125, 34.375, 23.125),
0.25: GFSKFreqDevSpec(
0.25, 4.0, 46.25, 56.25, 68.75, 46.25),
0.5: GFSKFreqDevSpec(
0.5, 2.0, 92.5, 112.5, 137.5, 92.5),
1.0: GFSKFreqDevSpec(
1.0, 1.0, 185.0, 225.0, 275.0, 185.0),
2.0: GFSKFreqDevSpec(
2.0, 0.5, 370.0, 450.0, 550.0, 370.0),
4.0: GFSKFreqDevSpec(
4.0, 0.25, 740.0, 900.0, 1100.0, 740.0),
}
[文档]
@dataclass
class GFSKFreqDevResult:
"""GFSK 频偏校验结果。"""
min_ok: bool # 任意序列最小频偏是否达标
fd1_ok: bool # fd1 是否在 [fd1_min, fd1_max]
fd2_ok: bool # fd2 是否 >= fd2_min
ratio_ok: bool # fd2/fd1 >= 0.8
zero_crossing_ok: bool # 过零误差是否合规
@property
def passed(self) -> bool:
return all([self.min_ok, self.fd1_ok, self.fd2_ok,
self.ratio_ok, self.zero_crossing_ok])
[文档]
def check_gfsk_freq_dev(
symbol_rate_msps: float,
min_deviation_khz: float,
fd1_khz: float,
fd2_khz: float,
max_zero_crossing_error: float = 0.0,
) -> GFSKFreqDevResult:
"""校验 GFSK 频率偏差是否满足标准 8.2.2.1。"""
spec = GFSK_FREQ_DEV_SPECS.get(symbol_rate_msps)
if spec is None:
raise ValueError(f"不支持的符号速率: {symbol_rate_msps} Msps")
return GFSKFreqDevResult(
min_ok=min_deviation_khz >= spec.min_deviation_khz,
fd1_ok=spec.fd1_min_khz <= fd1_khz <= spec.fd1_max_khz,
fd2_ok=fd2_khz >= spec.fd2_min_khz,
ratio_ok=(fd2_khz / fd1_khz >= spec.fd2_fd1_ratio_min
if fd1_khz > 0 else False),
zero_crossing_ok=max_zero_crossing_error <= spec.zero_crossing_margin,
)
# ---------------------------------------------------------------------------
# 8.2.2.2 PSK EVM (表 53)
# ---------------------------------------------------------------------------
[文档]
class PSKModulation(IntEnum):
PI2_BPSK = 0
PI4_QPSK = 1
PI8_8PSK = 2
[文档]
@dataclass
class EVMLimit:
"""EVM 限值。"""
rms_pct: float
pct99: float
peak_pct: float
PSK_EVM_LIMITS: dict[PSKModulation, EVMLimit] = {
PSKModulation.PI2_BPSK: EVMLimit(20.0, 40.0, 45.0),
PSKModulation.PI4_QPSK: EVMLimit(13.0, 28.0, 32.0),
PSKModulation.PI8_8PSK: EVMLimit(9.0, 20.0, 25.0),
}
[文档]
def check_evm(
mod: PSKModulation,
rms_evm_pct: float,
pct99_evm_pct: float,
peak_evm_pct: float,
) -> bool:
"""校验 PSK EVM 是否满足表 53 限值。"""
lim = PSK_EVM_LIMITS[mod]
return (rms_evm_pct <= lim.rms_pct
and pct99_evm_pct <= lim.pct99
and peak_evm_pct <= lim.peak_pct)
# ---------------------------------------------------------------------------
# 8.2.2.3 频率容限 (表 54/55)
# ---------------------------------------------------------------------------
[文档]
class FreqBand(IntEnum):
BAND_2400 = 0
BAND_5100 = 1
BAND_5800 = 2
SUB_1G = 3
[文档]
@dataclass
class FreqToleranceSpec:
"""频率容限规范。"""
freq_offset_khz: float
freq_drift_khz: float
drift_rate_hz_per_us: float
# 表 54: 2400 MHz
FREQ_TOLERANCE_2400: dict[str, FreqToleranceSpec] = {
"GFSK": FreqToleranceSpec(150.0, 50.0, 400.0),
"BPSK/QPSK": FreqToleranceSpec(48.0, 10.0, 34.0),
"8PSK": FreqToleranceSpec(48.0, 10.0, 11.0),
}
# 表 55: 5100/5800 MHz
FREQ_TOLERANCE_5G: dict[str, FreqToleranceSpec] = {
"GFSK": FreqToleranceSpec(175.0, 100.0, 400.0),
"BPSK/QPSK": FreqToleranceSpec(120.0, 20.0, 34.0),
"8PSK": FreqToleranceSpec(120.0, 20.0, 11.0),
}
# sub-1GHz: ±100 ppm
SUB_1G_TOLERANCE_PPM = 100.0
[文档]
def check_freq_tolerance(
band: FreqBand,
mod_type: str,
freq_offset_khz: float,
freq_drift_khz: float,
drift_rate_hz_per_us: float,
carrier_freq_mhz: float = 0.0,
) -> bool:
"""校验频率容限是否满足 8.2.2.3。
:param band: 频段类型。
:param mod_type: 调制类型 ("GFSK", "BPSK/QPSK", "8PSK")。
:param freq_offset_khz: 初始频率偏差 (kHz)。
:param freq_drift_khz: 频率漂移 (kHz)。
:param drift_rate_hz_per_us: 频率漂移率 (Hz/us)。
:param carrier_freq_mhz: 载波频率 (MHz), sub-1GHz 时用于 ppm 校验。
"""
if band == FreqBand.SUB_1G:
if carrier_freq_mhz <= 0:
return False
max_offset_khz = carrier_freq_mhz * SUB_1G_TOLERANCE_PPM * 1e-3
return abs(freq_offset_khz) <= max_offset_khz
table = (FREQ_TOLERANCE_2400
if band == FreqBand.BAND_2400
else FREQ_TOLERANCE_5G)
spec = table.get(mod_type)
if spec is None:
raise ValueError(f"不支持的调制类型: {mod_type}")
return (abs(freq_offset_khz) <= spec.freq_offset_khz
and abs(freq_drift_khz) <= spec.freq_drift_khz
and abs(drift_rate_hz_per_us) <= spec.drift_rate_hz_per_us)
# ---------------------------------------------------------------------------
# 8.2.2.4 / 8.2.2.5 时钟精度
# ---------------------------------------------------------------------------
ACTIVE_CLOCK_PPM = 20.0
ACTIVE_CLOCK_JITTER_US = 2.0
SLEEP_CLOCK_PPM = 500.0
SLEEP_CLOCK_JITTER_US = 16.0
[文档]
def check_clock_accuracy(
ppm: float,
jitter_us: float,
is_sleep: bool = False,
) -> bool:
"""校验时钟精度 (8.2.2.4 / 8.2.2.5)。"""
max_ppm = SLEEP_CLOCK_PPM if is_sleep else ACTIVE_CLOCK_PPM
max_jitter = SLEEP_CLOCK_JITTER_US if is_sleep else ACTIVE_CLOCK_JITTER_US
return ppm <= max_ppm and jitter_us <= max_jitter
# ---------------------------------------------------------------------------
# 8.2.3 无用发射 — GFSK 频段内杂散 (表 56/57)
# ---------------------------------------------------------------------------
[文档]
class ChannelBandwidth(IntEnum):
BW_100K = 100
BW_125K = 125
BW_250K = 250
BW_500K = 500
BW_1M = 1000
BW_2M = 2000
BW_4M = 4000
[文档]
def gfsk_inband_spurious_limit_dbm(
bw: ChannelBandwidth,
k_offset_mhz: float,
) -> float:
"""计算 GFSK 频段内杂散限值 (dBm)。
对宽信道 (1/2/4 MHz, 表56) 返回 dBm/MHz;
对窄信道 (100-500 kHz, 表57) 返回 dBm/100kHz。
"""
abs_k = abs(k_offset_mhz)
if bw == ChannelBandwidth.BW_1M:
if abs_k == 2:
return -20.0
return -30.0
if bw == ChannelBandwidth.BW_2M:
if abs_k == 4:
return -20.0
if abs_k == 5:
return -23.0
return -30.0
if bw == ChannelBandwidth.BW_4M:
if abs_k in (7, 8, 9):
return -20.0
if abs_k == 10:
return -23.0
return -30.0
# 窄信道 (100/125/250/500 kHz, 表57)
bw_mhz = bw / 1000.0
first_adj = {100: 0.2, 125: 0.25, 250: 0.5, 500: 1.0}
f1 = first_adj.get(int(bw), bw_mhz)
if abs_k <= f1:
return -30.0
return -40.0
# ---------------------------------------------------------------------------
# 8.2.3.2 PSK 频谱模板 (表 58/59)
# ---------------------------------------------------------------------------
[文档]
@dataclass
class PSKSpectrumMask:
"""PSK 发射频谱模板中各参考偏移频率 (MHz)。"""
bw: ChannelBandwidth
f1_mhz: float
f2_mhz: float
f3_mhz: float | None = None # 窄信道仅有 f1/f2/f3
f4_mhz: float | None = None # 宽信道有 f4
# 表 58 — 宽信道
PSK_SPECTRUM_MASK_WIDE: dict[ChannelBandwidth, PSKSpectrumMask] = {
ChannelBandwidth.BW_1M: PSKSpectrumMask(
ChannelBandwidth.BW_1M, 1.0, 1.5, None, 2.5),
ChannelBandwidth.BW_2M: PSKSpectrumMask(
ChannelBandwidth.BW_2M, 2.0, 3.5, 4.5, 5.5),
ChannelBandwidth.BW_4M: PSKSpectrumMask(
ChannelBandwidth.BW_4M, 4.0, 6.5, 9.5, 10.5),
}
# 表 59 — 窄信道
PSK_SPECTRUM_MASK_NARROW: dict[ChannelBandwidth, PSKSpectrumMask] = {
ChannelBandwidth.BW_100K: PSKSpectrumMask(
ChannelBandwidth.BW_100K, 0.1, 0.15, 0.25),
ChannelBandwidth.BW_125K: PSKSpectrumMask(
ChannelBandwidth.BW_125K, 0.125, 0.1875, 0.3125),
ChannelBandwidth.BW_250K: PSKSpectrumMask(
ChannelBandwidth.BW_250K, 0.25, 0.375, 0.625),
ChannelBandwidth.BW_500K: PSKSpectrumMask(
ChannelBandwidth.BW_500K, 0.5, 0.75, 1.25),
}
[文档]
def get_psk_spectrum_mask(bw: ChannelBandwidth) -> PSKSpectrumMask:
"""获取指定带宽的 PSK 频谱模板。"""
if bw in PSK_SPECTRUM_MASK_WIDE:
return PSK_SPECTRUM_MASK_WIDE[bw]
if bw in PSK_SPECTRUM_MASK_NARROW:
return PSK_SPECTRUM_MASK_NARROW[bw]
raise ValueError(f"不支持的信道带宽: {bw}")
# ---------------------------------------------------------------------------
# 8.3.1.1 参考灵敏度 (表 60/61)
# ---------------------------------------------------------------------------
_BW_KEYS = [100, 125, 250, 500, 1000, 2000, 4000]
# 表 60 — GFSK 灵敏度 (dBm)
GFSK_SENSITIVITY: dict[int, float] = {
100: -79.9, 125: -79.0, 250: -76.0, 500: -73.0,
1000: -70.0, 2000: -67.0, 4000: -64.0,
}
# 表 61 — PSK 灵敏度 (dBm), 按 (MCS, bw_khz) 索引
PSK_SENSITIVITY: dict[tuple[int, int], float] = {}
_PSK_ROWS = [
(0, [-90.9, -90.0, -87.0, -84.0, -81.0, -78.0, -75.0]),
(1, [-88.9, -88.0, -85.0, -82.0, -79.0, -76.0, -73.0]),
(2, [-87.9, -87.0, -84.0, -81.0, -78.0, -75.0, -72.0]),
(3, [-85.9, -85.0, -82.0, -79.0, -76.0, -73.0, -70.0]),
(4, [-84.9, -84.0, -81.0, -77.0, -75.0, -72.0, -69.0]),
(5, [-82.9, -82.0, -79.0, -75.0, -73.0, -70.0, -67.0]),
(6, [-81.9, -81.0, -78.0, -74.0, -72.0, -69.0, -66.0]),
(7, [-79.9, -79.0, -76.0, -72.0, -70.0, -67.0, -64.0]),
(8, [-75.9, -75.0, -72.0, -68.0, -66.0, -63.0, -60.0]),
(9, [-78.9, -78.0, -75.0, -71.0, -69.0, -66.0, -63.0]),
(10, [-77.9, -77.0, -74.0, -70.0, -68.0, -65.0, -62.0]),
(11, [-75.9, -75.0, -72.0, -68.0, -66.0, -63.0, -60.0]),
(12, [-69.9, -69.0, -66.0, -62.0, -60.0, -57.0, -54.0]),
]
for _mcs, _vals in _PSK_ROWS:
for _bw, _v in zip(_BW_KEYS, _vals, strict=True):
PSK_SENSITIVITY[(_mcs, _bw)] = _v
[文档]
def reference_sensitivity(
bw_khz: int,
mcs: int | None = None,
) -> float:
"""返回参考灵敏度 (dBm)。
:param bw_khz: 信道带宽 (kHz)。
:param mcs: MCS 索引。None 时返回 GFSK 灵敏度。
"""
if mcs is None:
val = GFSK_SENSITIVITY.get(bw_khz)
if val is None:
raise ValueError(f"不支持的 GFSK 带宽: {bw_khz}kHz")
return val
key = (mcs, bw_khz)
val = PSK_SENSITIVITY.get(key)
if val is None:
raise ValueError(f"不支持的 PSK 灵敏度查询: MCS={mcs}, BW={bw_khz}kHz")
return val
# 8.3.1.2 最大输入电平
MAX_INPUT_LEVEL_DBM = -10.0
# ---------------------------------------------------------------------------
# 8.3.2.1 接收机选择性 (表 62/63)
# ---------------------------------------------------------------------------
[文档]
@dataclass
class SelectivityEntry:
"""接收机选择性要求条目。"""
signal_level_offset_db: float # 相对于参考灵敏度的偏移
interferer_level_dbm: float
freq_offset_type: str # "0", "f1", "f2", "f3+", "image", "image_f1"
# 表 62
SELECTIVITY_TABLE: list[SelectivityEntry] = [
SelectivityEntry(3.0, -88.0, "0"),
SelectivityEntry(3.0, -82.0, "f1"),
SelectivityEntry(3.0, -50.0, "f2"),
SelectivityEntry(3.0, -40.0, "f3+"),
SelectivityEntry(3.0, -58.0, "image"),
SelectivityEntry(3.0, -52.0, "image_f1"),
]
# 表 63 — 干扰信号频率偏移 (MHz)
SELECTIVITY_FREQ_OFFSET: dict[int, tuple[float, float, float]] = {
100: (0.1, 0.2, 0.3),
125: (0.125, 0.25, 0.375),
250: (0.25, 0.5, 0.75),
500: (0.5, 1.0, 1.5),
1000: (1.0, 2.0, 3.0),
2000: (2.0, 4.0, 6.0),
4000: (4.0, 8.0, 12.0),
}
[文档]
def selectivity_freq_offsets(
bw_khz: int,
) -> tuple[float, float, float]:
"""返回指定带宽的 f1/f2/f3 频率偏移 (MHz)。"""
val = SELECTIVITY_FREQ_OFFSET.get(bw_khz)
if val is None:
raise ValueError(f"不支持的选择性带宽: {bw_khz}kHz")
return val
# ---------------------------------------------------------------------------
# 8.3.2.2 频段外选择性 (表 64/65/66)
# ---------------------------------------------------------------------------
[文档]
@dataclass
class OutOfBandEntry:
"""频段外选择性要求条目。"""
freq_range: tuple[float, float] # (start_mhz, end_mhz)
interferer_level_dbm: float
step_mhz: float
OOB_SUB1G: list[OutOfBandEntry] = [
OutOfBandEntry((30.0, 313.0), -35.0, 1.0),
OutOfBandEntry((929.0, 3000.0), -35.0, 1.0),
OutOfBandEntry((3000.0, 12750.0), -30.0, 2.5),
]
OOB_2400: list[OutOfBandEntry] = [
OutOfBandEntry((30.0, 2000.0), -30.0, 10.0),
OutOfBandEntry((2003.0, 2399.0), -35.0, 3.0),
OutOfBandEntry((2484.0, 2997.0), -35.0, 3.0),
OutOfBandEntry((3000.0, 12750.0), -30.0, 25.0),
]
OOB_5G: list[OutOfBandEntry] = [
OutOfBandEntry((30.0, 4500.0), -30.0, 10.0),
OutOfBandEntry((4510.0, 5130.0), -35.0, 10.0),
OutOfBandEntry((5870.0, 6490.0), -35.0, 10.0),
OutOfBandEntry((6500.0, 12750.0), -30.0, 25.0),
]
# ---------------------------------------------------------------------------
# 8.3.2.3 干扰互调 (表 67)
# ---------------------------------------------------------------------------
[文档]
@dataclass
class IntermodEntry:
"""接收机干扰互调要求。"""
bw_khz: int
signal_offset_db: float # 有用信号相对灵敏度偏移
interferer_level_dbm: float
n_values: tuple[int, ...] # |f2-f1| = n * bw_mhz
INTERMOD_TABLE: list[IntermodEntry] = [
IntermodEntry(bw, 6.0, -50.0, (3, 4, 5))
for bw in [100, 125, 250, 500, 1000, 2000, 4000]
]
# ---------------------------------------------------------------------------
# 8.3.4 RSSI 测量精度
# ---------------------------------------------------------------------------
RSSI_ACCURACY_DB = 6.0
RSSI_SIGNAL_OFFSET_DB = 6.0
# ---------------------------------------------------------------------------
# 8.4 UWB 射频要求
# ---------------------------------------------------------------------------
[文档]
def uwb_channel_center_freq_mhz(nc: int) -> float:
"""计算 UWB 物理信道中心频率 (MHz)。
fc = 499.2 + Nc * 124.8 (Nc in 0..79)
特殊信道: 125 -> 7542.6, 126 -> 18041.8, 127 -> 8541.0
"""
special = {125: 7542.6, 126: 18041.8, 127: 8541.0}
if nc in special:
return special[nc]
if 0 <= nc <= 79:
return 499.2 + nc * 124.8
raise ValueError(f"UWB 物理信道号超出范围: {nc}")
[文档]
@dataclass
class UWBSpectrumMask:
"""UWB 频谱模板参数 (8.4.3)。"""
pulse_width_ns: float # Tp
[文档]
def transition_band_hz(self) -> tuple[float, float]:
"""过渡带范围 [0.65/Tp, 0.8/Tp] Hz。"""
tp_s = self.pulse_width_ns * 1e-9
return (0.65 / tp_s, 0.8 / tp_s)
[文档]
def check_spectrum(
self,
freq_offset_hz: float,
psd_db_relative: float,
) -> bool:
"""校验 PSD 是否满足频谱模板要求。
- |f-fc| in [0.65/Tp, 0.8/Tp]: 低于峰值 10 dB
- |f-fc| > 0.8/Tp: 低于峰值 18 dB
"""
tp_s = self.pulse_width_ns * 1e-9
abs_offset = abs(freq_offset_hz)
boundary_lo = 0.65 / tp_s
boundary_hi = 0.8 / tp_s
if abs_offset < boundary_lo:
return True
if abs_offset <= boundary_hi:
return psd_db_relative <= -10.0
return psd_db_relative <= -18.0
# 8.4.4 UWB 频率容限与时钟偏差
UWB_FREQ_TOLERANCE_PPM = 20.0
UWB_CLOCK_TOLERANCE_PPM = 20.0
# 8.4.5 UWB 均方根误差
UWB_MAX_NRMSE_PCT = 25.0
UWB_MAX_NRMSE_DIFF_DB = 2.0
[文档]
def check_uwb_nrmse(
sync_nrmse_pct: float,
meas_nrmse_pct: float,
) -> bool:
"""校验 UWB 标准化均方根误差 (8.4.5)。
同步信号和测量信号 NRMSE 均不超过 25%,
且两者差值不超过 ±2 dB。
"""
if sync_nrmse_pct > UWB_MAX_NRMSE_PCT:
return False
if meas_nrmse_pct > UWB_MAX_NRMSE_PCT:
return False
# 差值以 dB 表示: 20*log10(ratio)
if sync_nrmse_pct <= 0 or meas_nrmse_pct <= 0:
return False
ratio_db = 20.0 * np.log10(sync_nrmse_pct / meas_nrmse_pct)
return abs(ratio_db) <= UWB_MAX_NRMSE_DIFF_DB
# ---------------------------------------------------------------------------
# 8.3.3 接收机杂散发射
# ---------------------------------------------------------------------------
[文档]
def check_rx_spurious_emission(
spurious_dbm: float,
limit_dbm: float = -57.0,
) -> bool:
"""校验接收机杂散发射 (8.3.3)。
标准要求接收机杂散发射应符合国家频谱法规。
默认限值 -57 dBm (30 MHz ~ 1 GHz 频段, GB/T 9254.1 限值)。
"""
return spurious_dbm <= limit_dbm
# ---------------------------------------------------------------------------
# 综合校验报告
# ---------------------------------------------------------------------------
[文档]
@dataclass
class RFComplianceReport:
"""射频合规综合报告。"""
power_class: PowerClass | None = None
power_step_ok: bool | None = None
gfsk_dev: GFSKFreqDevResult | None = None
evm_ok: bool | None = None
freq_tolerance_ok: bool | None = None
clock_ok: bool | None = None
spurious_ok: bool | None = None
sensitivity_dbm: float | None = None
selectivity_ok: bool | None = None
rssi_ok: bool | None = None
uwb_spectrum_ok: bool | None = None
uwb_nrmse_ok: bool | None = None
errors: list[str] = field(default_factory=list)
@property
def passed(self) -> bool:
"""全部已检项均通过时返回 True。"""
checks = [
self.power_step_ok,
self.gfsk_dev.passed if self.gfsk_dev else None,
self.evm_ok,
self.freq_tolerance_ok,
self.clock_ok,
self.spurious_ok,
self.sensitivity_dbm is not None,
self.selectivity_ok,
self.rssi_ok,
self.uwb_spectrum_ok,
self.uwb_nrmse_ok,
]
evaluated = [c for c in checks if c is not None]
return all(evaluated) and len(evaluated) > 0